From da41717cbfff63c0dc205185322c6a7fd57af870 Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 6 Dec 2021 19:10:33 +0800 Subject: [PATCH 001/105] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20java-9=20?= =?UTF-8?q?=E6=96=B0=E7=89=B9=E6=80=A7=E4=BB=A3=E7=A0=81(https://www.wdbyt?= =?UTF-8?q?e.com/2020/02/jdk/jdk9-feature/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-9/README.md | 6 ++ core-java-modules/core-java-9/pom.xml | 21 ++++++ .../wdbyte/java9/Jdk9CollectionFactory.java | 51 +++++++++++++ .../main/java/com/wdbyte/java9/Jdk9Http.java | 31 ++++++++ .../java/com/wdbyte/java9/Jdk9Interface.java | 37 ++++++++++ .../java/com/wdbyte/java9/Jdk9Stream.java | 71 +++++++++++++++++++ .../src/main/java/module-info.java | 5 ++ 7 files changed, 222 insertions(+) create mode 100644 core-java-modules/core-java-9/README.md create mode 100644 core-java-modules/core-java-9/pom.xml create mode 100644 core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9CollectionFactory.java create mode 100644 core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Http.java create mode 100644 core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Interface.java create mode 100644 core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Stream.java create mode 100644 core-java-modules/core-java-9/src/main/java/module-info.java diff --git a/core-java-modules/core-java-9/README.md b/core-java-modules/core-java-9/README.md new file mode 100644 index 0000000..16a83cc --- /dev/null +++ b/core-java-modules/core-java-9/README.md @@ -0,0 +1,6 @@ +## core-java-9 +当前模块包含 Java 9 新特性相关代码 + +### 相关文章 + +- [Java 9 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk9-feature/) \ No newline at end of file diff --git a/core-java-modules/core-java-9/pom.xml b/core-java-modules/core-java-9/pom.xml new file mode 100644 index 0000000..6b2d877 --- /dev/null +++ b/core-java-modules/core-java-9/pom.xml @@ -0,0 +1,21 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + core-java-9 + 1.0.0-SNAPSHOT + core-java-9 + jar + + + 9 + 9 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9CollectionFactory.java b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9CollectionFactory.java new file mode 100644 index 0000000..2399cc8 --- /dev/null +++ b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9CollectionFactory.java @@ -0,0 +1,51 @@ +package com.wdbyte.java9; + +import java.util.*; + +/** + * java 9 - 集合工厂方法 + * 集合工厂方法创建的集合都是不可变只读集合,在改变时会抛出异常。 + * 对于 Set 和 Map 工厂方法常见的,创建时如果有 key 值重复,也会直接抛出异常。 + * 对于 Set 和 Map 工厂方法常见的,每个 JVM 运行周期里遍历的顺序是不定的。 + * + * @author 达西 - 公众号:未读代码 + */ +public class Jdk9CollectionFactory { + + public static void main(String[] args) { + // 工厂方法创建集合 + List stringList = List.of("a", "b", "c", "d"); + Set stringSet = Set.of("a", "b", "c", "d"); + Map stringIntegerMap = Map.of("key1", 1, "key2", 2, "key3", 3); + Map stringIntegerMap2 = Map.ofEntries(Map.entry("key1", 1), Map.entry("key2", 2)); + + // 集合输出 + System.out.println(stringList); + System.out.println(stringSet); + System.out.println(stringIntegerMap); + System.out.println(stringIntegerMap2); + + } + + public void hashCodeOf(){ + // 工厂可以自由创建新的实例或者复用现有实例,所以 使用 of 创建的集合,避免 == 或者 hashCode 判断操作 + List stringList = List.of("a", "b", "c", "d"); + List stringList2 = List.of("a", "b", "c", "d"); + System.out.println(stringList.hashCode()); + System.out.println(stringList2.hashCode()); + } + + /** + * jdk 9 之前创建只读集合方式 + */ + public void jdk9Before() { + List arrayList = new ArrayList<>(); + arrayList.add("达西"); + arrayList.add("未读代码"); + // 设置为只读集合 + arrayList = Collections.unmodifiableList(arrayList); + // java.lang.UnsupportedOperationException + // arrayList.add("test"); + // Set,Map 同理 + } +} diff --git a/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Http.java b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Http.java new file mode 100644 index 0000000..63f6cbd --- /dev/null +++ b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Http.java @@ -0,0 +1,31 @@ +package com.wdbyte.java9; + +import java.io.IOException; +import java.net.URI; + +//import jdk.incubator.http.HttpClient; +//import jdk.incubator.http.HttpRequest; +//import jdk.incubator.http.HttpResponse; + +/** + * @author 达西 - 公众号:未读代码 + */ +public class Jdk9Http { + + /** + * 只有在使用 JDK9 时才可以放开注释语句,不然找不到类 + * + * @param args + * @throws IOException + * @throws InterruptedException + */ + public static void main(String[] args) throws IOException, InterruptedException { + //HttpClient client = HttpClient.newHttpClient(); + //URI uri = URI.create("http://www.tianqiapi.com/api/xxx"); + //HttpRequest req = HttpRequest.newBuilder(uri).header("User-Agent", "Java").GET().build(); + //HttpResponse resp = client.send(req, HttpResponse.BodyHandler.asString()); + //String body = resp.body(); + //System.out.println(body); + } + +} diff --git a/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Interface.java b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Interface.java new file mode 100644 index 0000000..fcbfe65 --- /dev/null +++ b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Interface.java @@ -0,0 +1,37 @@ +package com.wdbyte.java9; + +/** + * @author 达西 - 公众号:未读代码 + */ +public class Jdk9Interface { + public static void main(String[] args) { + ChinaPeople chinaPeople = new ChinaPeople(); + chinaPeople.sleep(); + chinaPeople.eat(); + chinaPeople.doXxx(); + } + +} + +class ChinaPeople implements People { + @Override + public void sleep() { + System.out.println("躺着睡"); + } +} + +interface People { + void sleep(); + + default void eat() { + drink(); + } + + default void doXxx() { + drink(); + } + + private void drink() { + System.out.println("喝水"); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Stream.java b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Stream.java new file mode 100644 index 0000000..b8fe9ab --- /dev/null +++ b/core-java-modules/core-java-9/src/main/java/com/wdbyte/java9/Jdk9Stream.java @@ -0,0 +1,71 @@ +package com.wdbyte.java9; + +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +/** + * * @author 达西 - 公众号:未读代码 + */ +public class Jdk9Stream { + + /** + * takeWhile ,从头开始筛选,遇到不满足的就结束了 + * + * @param args + */ + public static void main(String[] args) { + // takeWhile ,从头开始筛选,遇到不满足的就结束了 + List list1 = List.of(1, 2, 3, 4, 5); + List listResult = list1.stream().takeWhile(x -> x < 3).collect(Collectors.toList()); + System.out.println(listResult); + // takeWhile ,从头开始筛选,遇到不满足的就结束 + List list2 = List.of(1, 2, 3, 4, 3, 0); + List listResult2 = list2.stream().takeWhile(x -> x < 3).collect(Collectors.toList()); + System.out.println(listResult2); + } + + /** + * dropWhile ,从头开始删除,遇到不满足的就结束了 + */ + public void dropWhile() { + // dropWhile ,从头开始删除,遇到不满足的就结束了 + List list1 = List.of(1, 2, 3, 4, 5); + List listResult = list1.stream().dropWhile(x -> x < 3).collect(Collectors.toList()); + System.out.println(listResult); + // dropWhile ,从头开始删除,遇到不满足的就结束 + List list2 = List.of(1, 2, 3, 4, 3, 0); + List listResult2 = list2.stream().dropWhile(x -> x < 3).collect(Collectors.toList()); + System.out.println(listResult2); + } + + /** + * 使用 ofNullable 创建支持 null 的 stream + */ + public void ofNullable() { + Stream stream = Stream.of(1, 2, null); + stream.forEach(System.out::print); + System.out.println(); + // 空指针异常 + // stream = Stream.of(null); + stream = Stream.ofNullable(null); + stream.forEach(System.out::print); + } + + /** + * 重载迭代器 + */ + public void iterate() { + IntStream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::print); + } + + /** + * Optional 转 Stream + */ + public void optionalToStream() { + Stream s = Optional.of(1).stream(); + s.forEach(System.out::print); + } +} diff --git a/core-java-modules/core-java-9/src/main/java/module-info.java b/core-java-modules/core-java-9/src/main/java/module-info.java new file mode 100644 index 0000000..4ceb417 --- /dev/null +++ b/core-java-modules/core-java-9/src/main/java/module-info.java @@ -0,0 +1,5 @@ +module net.codingme.feature.jdk9 { + exports com.wdbyte.java9; + //requires jdk.incubator.httpclient; + // 只有在使用 JDK9 时才可以放开上面的注释语句,不然找不到模块 +} \ No newline at end of file From fdf1106e38b435edbe2cb8b410f2909ffb690174 Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 23 Dec 2021 22:42:30 +0800 Subject: [PATCH 002/105] =?UTF-8?q?feat:=20Java=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E6=8F=90=E5=8D=87=E6=8A=80=E5=B7=A7(https://?= =?UTF-8?q?www.wdbyte.com/java/code-5-tips.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core-java-performance-code/README.md | 6 ++ .../core-java-performance-code/pom.xml | 35 +++++++ .../main/java/com/wdbyte/EnumIteration.java | 56 +++++++++++ .../java/com/wdbyte/EnumMapBenchmark.java | 92 +++++++++++++++++++ .../src/main/java/com/wdbyte/HashMapKey.java | 65 +++++++++++++ .../src/main/java/com/wdbyte/HashMapSize.java | 36 ++++++++ .../src/main/java/com/wdbyte/StringInJdk.java | 56 +++++++++++ 7 files changed, 346 insertions(+) create mode 100644 core-java-modules/core-java-performance-code/README.md create mode 100644 core-java-modules/core-java-performance-code/pom.xml create mode 100644 core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumIteration.java create mode 100644 core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumMapBenchmark.java create mode 100644 core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java create mode 100644 core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapSize.java create mode 100644 core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java diff --git a/core-java-modules/core-java-performance-code/README.md b/core-java-modules/core-java-performance-code/README.md new file mode 100644 index 0000000..28ac9e0 --- /dev/null +++ b/core-java-modules/core-java-performance-code/README.md @@ -0,0 +1,6 @@ +## core-java-performance-code +当前模块包含代码性能相关代码 + +### 相关文章 + +- [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) \ No newline at end of file diff --git a/core-java-modules/core-java-performance-code/pom.xml b/core-java-modules/core-java-performance-code/pom.xml new file mode 100644 index 0000000..b998e88 --- /dev/null +++ b/core-java-modules/core-java-performance-code/pom.xml @@ -0,0 +1,35 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + core-java-performance-code + 1.0.0-SNAPSHOT + jar + + + 1.8 + 1.8 + + + + + + org.openjdk.jmh + jmh-core + 1.33 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.33 + provided + + + + \ No newline at end of file diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumIteration.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumIteration.java new file mode 100644 index 0000000..c4a708f --- /dev/null +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumIteration.java @@ -0,0 +1,56 @@ +package com.wdbyte; + +import java.util.EnumSet; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * 枚举类遍历测试 + * + * @author https://www.wdbyte.com + * @date 2021/12/07 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 5, time = 3) +public class EnumIteration { + + enum FourteenEnum { + a, b, c, d, e, f, g, + h, i, j, k, l, m, n, + o, p, q, r, s, t, + u, v, w, x, y, z; + + static final FourteenEnum[] VALUES; + static { + VALUES = values(); + } + } + + @Benchmark + public void valuesEnum(Blackhole bh) { + for (FourteenEnum value : FourteenEnum.values()) { + bh.consume(value.ordinal()); + } + } + + @Benchmark + public void enumSetEnum(Blackhole bh) { + for (FourteenEnum value : EnumSet.allOf(FourteenEnum.class)) { + bh.consume(value.ordinal()); + } + } + + @Benchmark + public void cacheEnums(Blackhole bh) { + for (FourteenEnum value : FourteenEnum.VALUES) { + bh.consume(value.ordinal()); + } + } +} + diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumMapBenchmark.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumMapBenchmark.java new file mode 100644 index 0000000..a0da851 --- /dev/null +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/EnumMapBenchmark.java @@ -0,0 +1,92 @@ +package com.wdbyte; + +import java.util.EnumMap; +import java.util.HashMap; +import java.util.SplittableRandom; +import java.util.UUID; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 5, time = 3) +public class EnumMapBenchmark { + + enum AnEnum { + a, b, c, d, e, f, g, + h, i, j, k, l, m, n, + o, p, q, r, s, t, + u, v, w, x, y, z; + } + + /** 要查找的 key 的数量 */ + private static int size = 10000; + /** 随机数种子 */ + private static int seed = 99; + + @State(Scope.Benchmark) + public static class EnumMapState { + private EnumMap map; + private AnEnum[] values; + + @Setup(Level.Trial) + public void setup() { + map = new EnumMap<>(AnEnum.class); + values = new AnEnum[size]; + AnEnum[] enumValues = AnEnum.values(); + SplittableRandom random = new SplittableRandom(seed); + for (int i = 0; i < size; i++) { + int nextInt = random.nextInt(0, Integer.MAX_VALUE); + values[i] = enumValues[nextInt % enumValues.length]; + } + for (AnEnum value : enumValues) { + map.put(value, UUID.randomUUID().toString()); + } + } + } + + @State(Scope.Benchmark) + public static class HashMapState{ + private HashMap map; + private String[] values; + + @Setup(Level.Trial) + public void setup() { + map = new HashMap<>(); + values = new String[size]; + AnEnum[] enumValues = AnEnum.values(); + int pos = 0; + SplittableRandom random = new SplittableRandom(seed); + for (int i = 0; i < size; i++) { + int nextInt = random.nextInt(0, Integer.MAX_VALUE); + values[i] = enumValues[nextInt % enumValues.length].toString(); + } + for (AnEnum value : enumValues) { + map.put(value.toString(), UUID.randomUUID().toString()); + } + } + } + + @Benchmark + public void enumMap(EnumMapState state, Blackhole bh) { + for (AnEnum value : state.values) { + bh.consume(state.map.get(value)); + } + } + + @Benchmark + public void hashMap(HashMapState state, Blackhole bh) { + for (String value : state.values) { + bh.consume(state.map.get(value)); + } + } +} + + diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java new file mode 100644 index 0000000..1fc032e --- /dev/null +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java @@ -0,0 +1,65 @@ +package com.wdbyte; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author https://www.wdbyte.com + * @date 2021/12/06 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 5, time = 3) +public class HashMapKey { + + private int size = 1024; + private Map stringMap; + private Map pairMap; + private String[] prefixes; + private String[] suffixes; + + @Setup(Level.Trial) + public void setup() { + prefixes = new String[size]; + suffixes = new String[size]; + stringMap = new HashMap<>(); + pairMap = new HashMap<>(); + for (int i = 0; i < size; ++i) { + prefixes[i] = UUID.randomUUID().toString(); + suffixes[i] = UUID.randomUUID().toString(); + stringMap.put(prefixes[i] + ";" + suffixes[i], i); + // use new String to avoid reference equality speeding up the equals calls + pairMap.put(new MutablePair(prefixes[i], suffixes[i]), i); + } + } + + @Benchmark + @OperationsPerInvocation(1024) + public void stringKey(Blackhole bh) { + for (int i = 0; i < prefixes.length; i++) { + bh.consume(stringMap.get(prefixes[i] + ";" + suffixes[i])); + } + } + + @Benchmark + @OperationsPerInvocation(1024) + public void pairMap(Blackhole bh) { + for (int i = 0; i < prefixes.length; i++) { + bh.consume(pairMap.get(new MutablePair(prefixes[i], suffixes[i]))); + } + } + +} diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapSize.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapSize.java new file mode 100644 index 0000000..45ed900 --- /dev/null +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapSize.java @@ -0,0 +1,36 @@ +package com.wdbyte; + +import java.util.HashMap; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * @author https://www.wdbyte.com + * @date 2021/12/06 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 3,time = 3) +@Measurement(iterations = 5,time = 3) +public class HashMapSize { + + @Param({"14"}) + int keys; + + @Param({"16", "32"}) + int size; + + @Benchmark + public HashMap getHashMap() { + HashMap map = new HashMap<>(size); + for (int i = 0; i < keys; i++) { + map.put(i, i); + } + return map; + } + +} diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java new file mode 100644 index 0000000..ee0f6c1 --- /dev/null +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java @@ -0,0 +1,56 @@ +package com.wdbyte; + +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +/** + * @author niulang + * @date 2021/12/23 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 5, time = 3) +public class StringInJdk { + + @Param({"10000"}) + private int size; + private String[] stringArray; + private List byteList; + + @Setup(Level.Trial) + public void setup() { + byteList = new ArrayList<>(size); + stringArray = new String[size]; + for (int i = 0; i < size; i++) { + String uuid = UUID.randomUUID().toString(); + stringArray[i] = uuid; + byteList.add(uuid.getBytes(StandardCharsets.UTF_8)); + } + } + + @Benchmark + public void byteToString(Blackhole bh) { + for (byte[] bytes : byteList) { + bh.consume(new String(bytes, StandardCharsets.UTF_8)); + } + } + + @Benchmark + public void stringToByte(Blackhole bh) { + for (String s : stringArray) { + bh.consume(s.getBytes(StandardCharsets.UTF_8)); + } + } +} From 723eba69190c1ec99bfba30945166afd90499045 Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 23 Dec 2021 22:47:23 +0800 Subject: [PATCH 003/105] =?UTF-8?q?feat:=20Java=20=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E6=80=A7=E8=83=BD=E6=8F=90=E5=8D=87=E6=8A=80=E5=B7=A7(https://?= =?UTF-8?q?www.wdbyte.com/java/code-5-tips.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ core-java-modules/README.md | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dbcf7ba..99afeff 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,8 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - 锁的自动优化升级策略 ## 🔍 Java 性能分析 + +- [Java 中的5个代码性能提升技巧,最高提升9.5倍](https://www.wdbyte.com/java/code-5-tips.html) - [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) - [Java 中的监控与管理原理概述](https://www.wdbyte.com/java/monitoring.html) - [JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握!](https://www.wdbyte.com/2020/08/develop/tool-jmh/) diff --git a/core-java-modules/README.md b/core-java-modules/README.md index f5974c1..46cde3c 100644 --- a/core-java-modules/README.md +++ b/core-java-modules/README.md @@ -2,7 +2,15 @@ 当前模块包含 Java 核心代码 ### 相关文章 - +- [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) +- [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) +- [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/) +- [Java 14 新特性讲解](https://www.wdbyte.com/java/java-14/) +- [Java 13 新特性讲解](https://www.wdbyte.com/java/java-13/) +- [Java 12 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk12-feature/) +- [Java 11 新特性介绍](https://www.wdbyte.com/2020/03/jdk/jdk11-feature/) +- [Java 10 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk10-feature/) +- [Java 9 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk9-feature/) - [Java 8 函数接口 UnaryOperator ](https://www.wdbyte.com/java8/java8-unaryoperaotr) - [Java 8 函数接口 BiPredicate ](https://www.wdbyte.com/java8/java8-bipredicate) - [Java 8 函数接口 BiFunction ](https://www.wdbyte.com/java8/java8-bifunction/) @@ -18,5 +26,7 @@ - [Java 7 新特性 - 和低效 IO 说再见,Files,Paths,Path 文件操作介绍](https://www.wdbyte.com/2020/09/jdk/jdk7-file-pahs/) - [Java 7 新特性 - 新特性 - 快来补一波 Java 7 语法特性](https://www.wdbyte.com/2020/01/jdk/jdk7-start/) + - [Java 中的监控与管理原理概述](https://www.wdbyte.com/java/monitoring.html) -- [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) \ No newline at end of file +- [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) +- [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) \ No newline at end of file From 24b90f8d24feff3b3c168ed811746653a7df3b8c Mon Sep 17 00:00:00 2001 From: niumoo Date: Sun, 13 Feb 2022 21:08:03 +0800 Subject: [PATCH 004/105] =?UTF-8?q?feat:=20=E5=AD=97=E7=AC=A6=E5=9B=BE?= =?UTF-8?q?=E6=A1=88=EF=BC=8C=E6=88=91=E7=94=A8=E5=AD=97=E7=AC=A6=E7=94=BB?= =?UTF-8?q?=E4=B8=AA=E5=86=B0=E5=A2=A9=E5=A2=A9(https://www.wdbyte.com/jav?= =?UTF-8?q?a/char-image.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + core-java-modules/README.md | 4 +- core-java-modules/core-java-io/README.md | 6 + core-java-modules/core-java-io/pom.xml | 21 ++++ .../wdbyte/io/image/GeneratorTextImage.java | 104 ++++++++++++++++++ .../src/main/resources/bingdundun.jpeg | Bin 0 -> 22890 bytes .../core-java-io/src/main/resources/sanli.jpg | Bin 0 -> 136054 bytes .../core-java-io/src/main/resources/tiger.png | Bin 0 -> 46036 bytes 8 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 core-java-modules/core-java-io/README.md create mode 100644 core-java-modules/core-java-io/pom.xml create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImage.java create mode 100644 core-java-modules/core-java-io/src/main/resources/bingdundun.jpeg create mode 100644 core-java-modules/core-java-io/src/main/resources/sanli.jpg create mode 100644 core-java-modules/core-java-io/src/main/resources/tiger.png diff --git a/README.md b/README.md index 99afeff..867abaf 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 ## ⏳ Java 开发 +- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) - [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/) - [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/) - [三种骚操作绕过迭代器遍历时的数据修改异常](https://www.wdbyte.com/2021/02/develop/interator-update/) diff --git a/core-java-modules/README.md b/core-java-modules/README.md index 46cde3c..71d406b 100644 --- a/core-java-modules/README.md +++ b/core-java-modules/README.md @@ -29,4 +29,6 @@ - [Java 中的监控与管理原理概述](https://www.wdbyte.com/java/monitoring.html) - [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) -- [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) \ No newline at end of file +- [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) + +- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md new file mode 100644 index 0000000..07174d0 --- /dev/null +++ b/core-java-modules/core-java-io/README.md @@ -0,0 +1,6 @@ +## core-java-performance-code +当前模块包含 IO 相关代码 + +### 相关文章 + +- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml new file mode 100644 index 0000000..dfdad77 --- /dev/null +++ b/core-java-modules/core-java-io/pom.xml @@ -0,0 +1,21 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + core-java-io + 1.0.0-SNAPSHOT + core-java-io + jar + + + 1.8 + 1.8 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImage.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImage.java new file mode 100644 index 0000000..986ab3d --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImage.java @@ -0,0 +1,104 @@ +package com.wdbyte.io.image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + *

+ * 根据图片生成字符图案 + * 1.图片大小缩放 + * 2.遍历图片像素点 + * 3.获取图片像素点亮度 + * 4.匹配字符 + * 5.输出图案 + * + * @website https://www.wdbyte.com + */ +public class GeneratorTextImage { + + public static void main(String[] args) throws Exception { + BufferedImage image = resizeImage("/Users/darcy/Downloads/sanli.jpg", 120); + printImage(image); + } + + /** + * 图片缩放 + * + * @param srcImagePath 图片路径 + * @param targetWidth 目标宽度 + * @return + * @throws IOException + */ + public static BufferedImage resizeImage(String srcImagePath, int targetWidth) throws IOException { + Image srcImage = ImageIO.read(new File(srcImagePath)); + int targetHeight = getTargetHeight(targetWidth, srcImage); + BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics2D = resizedImage.createGraphics(); + graphics2D.drawImage(srcImage, 0, 0, targetWidth, targetHeight, null); + graphics2D.dispose(); + return resizedImage; + } + + /** + * 图片缩放 + * + * @param srcImagePath 图片路径 + * @param targetWidth 目标宽度 + * @return + * @throws IOException + */ + public static BufferedImage resizeImage2(String srcImagePath, int targetWidth) throws IOException { + Image srcImage = ImageIO.read(new File(srcImagePath)); + int targetHeight = getTargetHeight(targetWidth, srcImage); + Image image = srcImage.getScaledInstance(targetWidth, targetHeight, Image.SCALE_DEFAULT); + BufferedImage bufferedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); + bufferedImage.getGraphics().drawImage(image, 0, 0, null); + return bufferedImage; + } + + /** + * 根据指定宽度,计算等比例高度 + * + * @param targetWidth 目标宽度 + * @param srcImage 图片信息 + * @return + */ + private static int getTargetHeight(int targetWidth, Image srcImage) { + int targetHeight = srcImage.getHeight(null); + if (targetWidth < srcImage.getWidth(null)) { + targetHeight = Math.round((float)targetHeight / ((float)srcImage.getWidth(null) / (float)targetWidth)); + } + return targetHeight; + } + + /** + * 图片打印 + * + * @param image + * @throws IOException + */ + public static void printImage(BufferedImage image) throws IOException { + final char[] PIXEL_CHAR_ARRAY = {'W', '@', '#', '8', '&', '*', 'o', ':', '.', ' '}; + int width = image.getWidth(); + int height = image.getHeight(); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int rgb = image.getRGB(j, i); + Color color = new Color(rgb); + int red = color.getRed(); + int green = color.getGreen(); + int blue = color.getBlue(); + // 一个用于计算RGB像素点灰度的公式 + Double grayscale = 0.2126 * red + 0.7152 * green + 0.0722 * blue; + double index = grayscale / (Math.ceil(255 / PIXEL_CHAR_ARRAY.length) + 0.5); + System.out.print(PIXEL_CHAR_ARRAY[(int)(Math.floor(index))]); + } + System.out.println(); + } + } + +} diff --git a/core-java-modules/core-java-io/src/main/resources/bingdundun.jpeg b/core-java-modules/core-java-io/src/main/resources/bingdundun.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8243a8a745e076f571efcbedfc1611ba50a7c9fe GIT binary patch literal 22890 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%0A@b|>=TI+UK@@|gz@b*9YHQ67`)Tg-WQ0mpgb zVR<6+{)g(UVyWWtu58tnr-H;u?~;UN=goX;!uJ$ojD-2j6B~-##+GN~gObV|9>5zBn$&YY-*Hio8 znsQi1S|v;_aF5j`g_90t|MZJoXR%;XaS=D6sXR)FlOkVf9qx=5Gwzw|qZhTm7GXq* z7K2lLAaGn{Vw^oj?tfp33-Ky-ZX{no_=mP`qAe==f$lFCjKXI=afo@RkqU{dx^XeG zpGM-mXE|z;UyBS_vhm?>Fnm<9ZP#BM7JNB3(1)#LW^@U!3!x=3SOt)!V$*8g$yfk4 z^~N=-ebtq`24vaXrj`p=dxfI1NI)U(J_E|D;J)BKqLb(@*R0sk7MHY(w2Rzew0|k% zFa2+#-FJ)cBWA^h$(1==YX5TL$+cw4`zS1~qq1Jo;XKAn6D@IS!_W<2BdoI%YPUWr zCD0Npd=JjJGO6gr#s`>HqN@Aj=8RScXSxS5+H`$BqLO-umtup6Dd~>&{an5X-;$E~ z;=>|LOd%qclk(OpLvrVX({aZb3{fdWR^Z3hZ@R?&u#9Bc> z+Ais~P#T{0qf{|S8JW-6wnG|k0ILrTv*z8W#S1nRmH?>%3OSPA#@C$ zunGDvA_P3*Jqvh>eul!?E-8{H%lUO`G53Ejojh{byYrNJceRD$L5YzAl=V){Xy)bT z-So#*7k6?^obM)7ka4RrG}U~~`9}DjVzQ-q@9_@};X6r{H&d-O!H_6Dh6_Te_a1Xd z{cj!To$`=dWA^*cRb6$GfJBlxF5puc)u|G5_{q1sAG6cegifmwo5?~dsq zWny%+g_IWgnr9-}yFH8GEe1E#O#Bkr89GbZuHe%n1K!@+n!JKU`^8sYR>FajZ(R}_ z^WcXBI3ZVK-U)%f;*IM!V(-hs*SGShGPgqPg0hU`gTLdwNQ|{zDy8U^;zr6Ycs$=W z(3L|qBRS4}txU;WF-Kdxb6($*DnIvnH7><}U`KRZ5qn|v*jL$F@K@n<<>%?F0V#1O zd{l~;JjXdpgD)?W7iVY%_*aX0dU{o76uy1zRcJh5RcP{^>#Sit{kYpOvrHMintF^o zrmGN8W4!D%-3y&4I>QZm_9>u-B#uw}#i%aB=BeNESL}hM-!oYlMo`70$dGNmK~CSc)E|mzAMWoCvpg-3tLtcJ8i8{LNT79y z*z#=MZAvvT$@bHbPM5)`pMx{sUI?twmN0rTuiX416}b~0BZK$r9Y>j#51ANBNwg-- z&qDB%zpEX5WJF8hYZ)$se*9~sj z%{smvvAqZ$-Fz(wuSeWw!x0FrSU20h_gGmQxoMVs70-N*I@>mT{^R6pub#><^+X)m z6L+i{hh6ug;|x=3T6A$VWS88`0nOG0e>pC)^V_y;VJMU)q4}qa23*1JP7YT3MD?gW zkxhBCd?38!8o0gH8v5%)1lJEKS6n$rAZ&gmYU|#Vn2;asEz$$UW;3D^jx2l`(YyG^mIb5*y*>p(}cjlmA9r|XP&ntKWXW_Cf&WLgKFZGy zTg=6<_suV`dosZBcj=TRvDZm0RqY>O1GG|07m^b-ispKzfzK0Us_vg>$g>I@s?_38 zNHZOcX|dzVSoHS|z9+>PC-dKRo|I|3X`_oLFkX*cNu{JGL}_c$MCvJDy~)oS+r`HW z(LR+g$J1jCInaszV4KgHEIV}#@OS*(xc*bTP?Fu2B|uQ&jV6A!gOj2ep}dK(1}J2m z6+UtvRsZ^+!so||-!t076ZPof=eY5WY?$VBQ{SK_5K4J_h8H50_U?{uYS^gsXwrJ- zQB~Ngn`=OAC(rw%8ca&*(9da-q}^PQnLv4_lVSHC$eCkoM@UoWjwRO;5Vk-~7ZFtB z`Ye55E}LH)J&b&S*iG-Nn(HGB*7I?*vLv2>gam-zxrsq@4^vk_gW25LC{w7{&H41n z>D<|C>1&TM=_522ZSHly5u)|ImBwn6Q9}g%!QETIPESrZASR$ zS&PX~ZDE(XB%y!L`|e*p?XZw&cPsWaG9l>OuU~ws{r=09)a?VV5e!yU>a&>Ub3$=n zIvwAF+qaqO&z|hntxF-W&36%ZZ-Z-7$xl zPfPSzOD2#$>pwJp?gI#FS3IGCgvV<2T8aK zA=XSFlY&@%KGW2= zZZ+e1Wr{GhSR@yj)Tg!&lLlaviD6Qe?Wi2s$?WJy9$6->{}!N@Sgwcgo=zww4kltq z@t8`hpq1}HqO1Ar_80Oh#9gyB%gc}H48xxm-%4T<8*ot|!S$UPi%Yde@O~zZ!D)j% zexg#Ds*A;5&zl<2Ev=6+{_^a3+9gk39fKJmPn%B~?j!D5BXxHia&AGB0b`mbXyBkeY>fyWb$>)pYc?E)XRgnQrK9SEB+oyO(maF^^Y*h>chIHz|>DjjZ1K$S00y>hQGP<`tER39(l6s`8daWqW^`aoj z@)NlGIscMFk_SAIRsOC{3nQFmBHVR^WcU@K>Bh6}{@E2~ zk|_(1ge5t8p1BL2^6nt_*U3%93sr$d1Fh~n90P|X2-Cvp(2ECVw(GC1L1F2r7;Jod$>6VJ#9d`q!C ziUpEgQK?PU4PB>~?`D=e3rE?xd__D9igcQi`{RKF{lyn^%~X}m3qA(qc3TVhE;25) zSQ)HF1`Ny6!Mie$Z2@Kf1hK{SGoxl@UkBiPqBcMviSMUmiA&k-m`WGFE!Fb3esKH5 z!dNEm@h%qvH$VK3dCaXUa?}H!?R*|@&vftTB7S1#&k#$MzYW%30>mnJ*k1gwmCmnI zFBN4^hK%VbGU0IMN3b}L$7!Omk+YEjUwIKLaUcoDe7cf=!K7t5%H-o5BWqz> z+X8FbTv7n@6~>|tGIC2A_Zo02tOOkD6W_tg!D!Y)?P#Tq%}s|8hX%m2DO;KuK@ zma)Qd6|`t~#NLs;aLSebFn9PGKuWp%ypz83=#$hL$NA(@=x)u&U1tV@(I4N?#2&E_ z;=;VFBaXwfR|BX!c>jT z9(q#svba-ykW%#%x;Iz#hs3GFYoEj$yiTh6O+T}cmR1N#sH|rA>=;Dy{r-(AZb>Ol zs+~HOxRPJlw*0zBsZY^D=Eb;dx_1YoZ()W%{ukV@gZ!5hvd_1>H@%MTB=^((^^|7P zS66V>aLI_caXj_aYIIc!W-N{?Oxq?IL- zlcgV@j;r}J`Dg-XcC?QX_xL0ADYW1{O8llO0#LoSdTXl)`$!A2+jgktQw%y@#B3k4 zM(~*Gf;Ian@9OwFGaLJ;3e=_TZcm!);pb5C`&e45;1Yv*_{}f;X_~a@Dzg~J0{lGP zW+jpYcs`!bCfM zb4m=c^eV?FdwaussK<>WnUj&{tcW&$`$xI*dAM7rTh9$Y3Mx03n+1>*SLB&xk5~ zuK}FL5p%i~CiGpKvrJgzf1+KA-%q+~x74rT3Aa)|)v#7Lx$H zOZ>3WUf=EC^#IgTWbeUJh_vVsN9F{Z2v$ez15xvPhDl;BBWp4#;AejgU?0(wTD0xq z#0!Y63J^)1);#ySlni}!2KA6cUGARdExzDGpN|c4Z(TyazqDzUm_yc1??OX9Hpwi> zWfwW!R;sqOae&mT-J2*w-o~*&_gRKA2gQ&1ShiJtD`%(o4z2jgXS4s(@zj2`+#&?mAYuGZK>iK_%T!D zyP*X?G|cg%pUdwPpZqK=P_CrF__F?iRTMl@9E*(wm6u&W%`7l5|88ZO4t>YaO6@T9 zbFo>ZxmC;#d&@|nJvFyz5nJx?LDs@At4)n(jVg$jv#k-*)?NeRLd5)DgUGB6m*Ps_ zcE+~fY}2(rkQ-GryXLZSVl}DDb8Bc>!ILe4n0g|6LA)_VZrCisKZ8H_C+-;FuD*8u zYAMTDcp!=>6>Ce&flFW~hBk^JzXmjKiY<3gHcvx!*1o@f9kZ!@hYgjI} zb1}G#$ysBP8Dr|`{9R5SD)m$4VwV=`C1#oeocSm)=hV5)$>QZ_+-&5Y{W;|T;U%XE z$#${i|0Rok6JY$wGR`wCd5%nr+TaS5#c1`FEm4MHbmi zCpvOO$x?6@|2*^NW>1ZF0UH0r)^x%ytrjZMmfUYG#3WJeh8WR*<5BG(k}joo1Z!B14n-`L}2ab4-8CFP^!AaSnd&mw|HCjyO$Y`lS^Vmw=W~$hc%~`1Qn< z1b7Cu>F1>uJCad7m$e&jDJ!_8e?FV{x#4(de7vjHbEGsDY45(Gs{kC)7 zI`6C9r+sBKKr*iyAgIovjhqb$4SQ`u_0|%XHaU6wUO^sbjraE}@*I{jxdAP|Tm9r| z_wOSyX=!<%Q#d5&@; z-0SSSZ#jb#o!>PeJ?_Zy$u<{rSdk3S0dsL4XV;$ELWut6#6yM^=Gc$xB24Xb-_Iit8k;0I!-<&5Hst!?HmXM<9Ccs6T zI-k7PfOVlfzwk!uV{fC0q+Teo_H3C=%*sgnr6FR4B%CmKNt!;;Bt&XS-A%BV$SBuT zc8DYW6>2^1$Ij|hhX+LefPeq|xTZARaRJ6ie%eBv&D?MQL05JKoNO#Y4xDgel!6q7 z9!zd3|A=4Qjurg)_VnQjf4}FHb|@y}$h&tTXLdd#1@we(R~};H>GVF1m3BSGEaur0 zyu#(lz1>bW9HLHVkDg^CU>Bzr`L!)<@-meV(Gx$nN**nR$LIc^6ryaed0va)Ufw@IK?@2;?Rq@$}W|Q?ccDEXMKZ+gmoZ z%VTrd%B&8?>!{oUJf!!Q;2L97Cq#1Q%27MP49^R4sUx9Tf0EiYoizuqNZ?Qy5}85o@z$ntYJNp-Dw&1 zB1K>POE=4WNrYHQwOw*}ZRO19`iv^&NrIs>;p+}Zd~}A1&|sZ|?`{KO4>yj%tx;~e zRzN?1ybE!pVy~ z9A0k+p9(s~zoh4xBD+iFULU*kk}X=Jez=9Xhj0Zo4N2#oU0iOLp{-KS&XMQ%Jr0)# zSN-BjTU|V#OdVAXZ!knNdX4|>;Jd*FbzXu7gy%wG%z!D1Zv5%$%p>};p0^s$qzGd= z5LQv1Tm2$W-@)DI?P3Q@o24&#_HmaNK0XKyP;8D(!HDP9`fZtGdX;#D>(F}n`EruV zBd=zVahh?l&XQ5JevX~~*!zn*Cw+fSUZ-9DVPdx~rA&G_?Wyf=x%Tw?P4bd&XM-o> zwf5yO{!v~hz{|pfHmR_h3~$RnUY57a@MVVD}fBI zcV#+m>@+@2P}-9E$hAc5u&lnf?Grt>TOZ`e6)h-c;&Lvj7Ik6hqUAB?U$=2~Fbd%}S z?^oa*dGoloHNOl1>9=}q3UN;=k}&m9{ljqL03>4wYPf|;qM}GymTk7Z{?q9PinRA8 zO8#-kAUfO9)2LWxy&YxVdg$N6+ek@nIiYOh9`xtV_Y*Kg{_%jcF!B3MCY`hE-3)Fv00iY!09^ccU&pP=f2M~O zAJ{A6koXG@{vzT=Hzo}$s5~$6s{bdp+bo=Vh7vF=m?~vvoA7z$pD@*N$yXe9=^Fh` z&)d?Xk}|k&R+d>fd=7|7Y9BD1x_*6gTgdesRn0AYHg;uPW(}$TJ*H6`@koY}FvX(R?R9 zIZ1Zj

$T2Geh|&Px8L{4lF@En+~A&jIRZJP)W0(n}UyKhh2UjW{1*8ssm+!{Ee zHoS+ApcaN_=hO{*5BjR;^<6idvcGjbARi(LUVppVRdS5$@Op59##0k9ogH&>jxoO_ zlGb}%yQx&n$QYu8P7@_p0Bq*b#b!hskW=MY9)dqlN=%jgHrvD{35y=;rigQKf*v>J z1vs5r*UOXYznR{oYj7N)$QC|^DG{djcj{ZokeA@8dd%`(^IQ}raz%TMGRgE}bE7?H z*4a=i<<^+6MoQ|3R(F!Ph#Q}Ljib`D2UXS91#_uzYE?>;o|V63?72k2m?YCbo@_x;ckg>ruwhbs^cQduuSk3~?>|6rSrmNJlA(IW%t*hOpW=ssqUba9jCIld zAlUWi&<0(aiqq7Q-sZ$c3AL8SOxq>%Z8rF~%&t3J&fqYdD=jw*;Tr^>zV~WLh>s-X zer;v2@6Ek(I}x;o*m!9e%3NC^C)Jfk4!VW*c$h^7=6|**%GL;dwLNnW3H3GYz?7ve zV+}Qbj1E9JVLG#%>ERE#XrdFnw< zzIZUEhDfqyWGZCAnj@xtW=*^c;^fUxs<5YtEuT2gTu^g+oNE-%+|d{6M$!ncwu?dqcSY=3$(%0$nYPPjy>!1gGG$+khN1@JY4SY^&W9UVcYL4y zh_k}bS#*vgkQuts*f|;q_IRmc?}~ZW*fGEu`QotF=0Xh(GPup=7&UDDPO@BU(?NB> zgts0%1m^naW4l&+n*ENI1&?opeH*2wHVFh>uvv%cR4NGzXqr85*&8Dn&3j(-d~Te3 z*0rz#k;-WVZb~Vks_B7S7N){&Z$;I8NG(ldE@l(T-Iq+TC-mmmL~9I#$HCj0DbYlm zVx7?WK^=7|`cL3rgW1TGevm21!ts|lD9*lSqeTATAW>vC0Q8$cO)ARxr*f6l;GPUz z7r+Xu3iq5b3RY|U*0nKHfI*(WPky|6$)T9x(B6xCUuqo$yDTaFU>zLbYFr>Q`!pl2 z>&{p9=5lg;eZozR;oX zCBze3OkS^5qV28pmt@40$TI8jKhjS)I93eoQua?QPWfUhIye{oO8wI>>!W)E>9>ob zL}fYEuLCo(V0v|TROqYqr215V^R2TL$quB>vfmoUW!18F5&e))Iq?d9%`PZwz;3Ac ztB;?i!acd!W^Le_9dZZ|l;R-w2sH3?wcE{M6cvE}-j3=Ho^)(p!a)sH!>%iR94;##F{eD`bW*4Z~Rr>@f9m>=enO4{m{V(k%5 z4J8hra%O&XXGgUda*U9iX!8OhnIs3L`Mw4-V&Vc+9A2a#``KEJ0gV6IA)VfS0S@&C#mA~dDj4O)zodjHZ+izXd`@st} zF{FII)tsE%`fbc16~M&xcw5no{&tvbPLYing=y-)yl`!L2Ex%(@0N~c`oCt)X11@LXkOQyy;f{zlFJ@j?~QBc5C<^ z?iuz&+wYEuFTc>e{2R6A>nF)7W& zl0HMDw|jY+H?^wO2;yC1g8y!Z3%_dEQ~ukNQ#Un*%g17pI~7h!|6cI$Mg{S|8lb&O z8~tEGHcC9wUdrxAdD#TG;vAnhBp1gdKVzyUx1s)~*rvP566MrBPG6VJpl)cnkz{j> zEZfDJja{j9Pl2K~)95xY!5`kF<0-TmmbCDt+}0!?MAG*>MFVpSE zqDm2*4Nuz^@1)EZAbupb{&J{c922EeoqoZWJ;y1N{?Y|BI@d@*l#Wx@#vsGFWyat8 z9Ru%ufu-O@OKq_Ps;9Xo%9cJR;@)%;FOx8=69Z}8EZDM2#wQ}y{(`YhId zsv5P?tXYizvd#^U?mKwy6RsJc;LnjJ;U8%b^W`jH#J3kb` zo90K@oAZtP)oQR!t-JM~^wgZ)ko=X_ERV)Agg;-{0`g1Q$9Dmp;503yowr}<1;h5* z(7nS2^;I=Ja@%dOdsJzCYESkOT>U*|=BVSi~D34Z@RY#?}h2FSvg z`%`Jj!85Cn66yIPXfx}TI0<&OTu#&$)0C|v(QJz@QdRKUs*hF-;MqZD(%r8%!~M{Z z@^{AS_>qfl!s)~)0=TjN5Q%w`iFIGAn?@6m$;VyxPH4_bNif`FMm6c%P~Ic%OTE_{ z-T;^gs2MWTJM^Ltff4w?2q<<0WN7FCq1^;Q*1kh(ek3S1%wX%X_jP!s}gc zh)&~#LDL_++L?kcYMI`p$L{Y9$eq0pd9J?a>fMPTdcsIOIv%cHR-au|=Ibm6OB@<} zMi)|1H~m@H+m$>&=UXF{MRl3(5ijb37DD%O`4=z;i8&=T@lIv=ybsZNp?ygfqO<{R zZkIjxok^5Th&N1MSIFuME2Rsxhzw2V;_?HD-vEFwdwU&bKBx~q!ibw=0ohB_{WRZT za8>*kxYN;d%A;taS2rEwD$h0I51Tv?Nro&7l5$UzS)NOB@HJwfcHPHCB z$#|EXb@Y8D+?k(fR8r-x!fHF}b}3Fq(Th>WLSc-p-~Dh14Jgel%-Cyp*$%@;Iaahr zq%Xp^okiJS4J&%bRe^(sTr!Bzqc)7k)8OG2Wl0L+mpltKz3e@+|CPGa>aY>PI?$|_ zNGOAK)cW>RU&aE6)m8#C{mGkjRYQ>nw)s`M%`6kIQ74MoVQJ_;r?~`PO9Si1yfu~T zbx3`df9SZ_I^Kjed`z5kJ0sn(?IeMXi7^lbD3UZt5mszN{xYuP47l^AaHfZM7el+6y1zHst>T zPz~k3d4P9w1c@S*=i1$8@*?mXRb?R(xbo?aini`Sqevpt3)A0P6Q}NY44avRA4rTSDzdLZD#sT{#kf2U*^UCj2jemuRaDGBllPXB_*%BvgHFw>8jC zr{5II5hU?J!=BLt>+lncP$lK${L~tG6QPHxk|4&T&MR$+n=#M7fSy(H!Wy0;B7=m| z*aa334NmpHfM{KB#_8T1Uc@mI>jn;NhKcO5Dxtv(&qX9))caF|tPO_V`Y+J=cthBo z)atV|N*m$kHrg(a>2sSk|LlqC%&iL%Ur8y-Zp42JN9ik_AlU7H2~$Nn_y$z(mJ9Ny zJ-D1GK71wsZB?&o03S1j%shUVHfv>G>^)|GV4gVOOGsx)tW%2*{h<+db-|ximhw+?b z|NZ7Hc8?i-*r>RCB z-P|&oa%FCT^sutG;~}yGTHO?BF5SJCfhwAXa-6xM`PPO~ib=O(KA zaT`hv=!vZBd)7!@t@y$;!4J*~k`k@fV?W7=7I_r0jR~Dt62@*7b8H{a%jF2 zaNd_jP5H6x-&@#dNSc3-wzh;Fmc8-S0aRg0TW&SWf27Y@uxFyBZ0^uGD4!k`t5pbg zu4ktiCwL&*YIEI$g*ufS-;r47(7H=RqY8sYxkfb2uzF&qI>da z;rfpKLo~RDVaJ0N?0pZVanqrSa|?4m=#`S$^d~Rd#FS~D(j6goSD|`gx;@`m4@&Bx z#H}sk(UUR8T90sBOwh-bBBiLltUWL`@#9A+%S-$XANONrXa=n9-%EqNz_RO~OEI>2 zYIw&~e{s^&g?mi9`5<%Sn0A602#0-)eY-Th&{EI4+*);Q-o)w)Xo#s>1fWCqczfrH zL#Zh+!;L$tqn75o-#%<_h_qyjA@cc+2EG(*Kh0mRZ>3pvCX%M3hK5z1Hx=1Kq2&x4jftlK>Fft{Ppo)9 zNk!ALUR|mL_w3gp7Er$|eKw08_RuJM4}BTyBuF8=Eh|-WP>|}KkKoD`danCp88UiM zj)pYPi6nC-Qn!2{H&HB2_&vs|3`(tEtX}eN7z{PuahCXD7nd{k1ikT zzX%UHlWyCfrqGQHcGX zSv(~+QG<@CPk>Eqgh-%d+bJTUg5WBa_@7vl*Z)PFiL0O86R`&bd$q!Zj#1umC6>TF*pWw@v?0u z6%XS)P^Uc98NoUcwTyu}k(@sQQ{%sEY;|BI-OW8*8M2RI?6;%Btzhl>j=G`&(kmTr zNt3^T9|$juKBc1tI&|lR(N|r_7g)gXa`A^wT_m7`?x#^rHv>oWI?E=rH zUVj07l2p&WPqW>IUd9g-G*|rddp~@Bxe`=Rl&aRS#j`{}mZUcrk81P|V?%n21kw84 z_Bu$PEjIM!dy=jf47vxast}TmNM7Uu`_^b%Tdti~zbJD$Vp78DoH|~Mkm)>2Pb$oX zL^0k{-9kJFwukL!^L<3Eo8<>nsOgp(m&X{@AM43*t)F_faC`JWch5tRhBbhca+gpj zTIZGj&K)@W%zii{Ws~;;S*_MdON`gSAgy?Tk&_Ha=>Q6k<^!+w5_#%QfyMUfiv3)*qe7OM7p(T76d81NF~;?Q7Xmo1e^VH*tt$nA^3aN6v

zN{`q;agJJeM-W&-eJ+Nl zYG!5K2GIH+AmCh8<&~M0Bd#1oReH^jrm%`=D?{S|VbMfMwevm~-p>Na#76#{e(rq?K=l4eZYm7MujO7_eO=2@xxbOS87brMWM}IfWfxiyFy0ulLE~Whi~Oc4Gjrf#|@%yTq{dasff6{ za*dpZ$IPha2R((AY9%nKeHO1J^-P2>yCo?)js&aX#-bjf)Ohxu>XFi1(Hv(&@Nwj$ zTKH7)@>J$1(WRJ_a0I}x)_Wi1QcKP(LxkQa=2}(otVF<}j18 zo-j$&Le$+M9C&GBQh@^Uz+h3gRf$<=H zCwuYGmgNB5JK=g^%u~k>O)=>0Ar2NAqvr5=s80S z=uMBRU|Q@{U$}ONR@&bZ$+5jmAs84lkT2^U9QBMd6myG2;*GG+IvNzZ8$323ub}@l zD#Z^eY_=Eiu!3jrPQ>tf+MJTmBb8}eNOy^zL21om0+$*{<$74NG~rVsF4K*NpI@nO zrG=5=vALC9lJ}OJFvc5QFrF|jCvVqr<5Mg3@wQ)i0Q1~rNFyXU1W2w&s8sM)fTdpi zeq)Yi)I}cq9c@O!mp(?>e*tJjC3H=?q!!7U4r@VrrSW@BtVHYiD#>p;O_sUAgP!TO zU|-rI_Cy7Hw?4ZANIH}AxgLMqn^&!|PGP4vvbkQ2S2NQjB2_PGUINYY;XTS(EHmds zcaX)XpP7Vl0|xwcli&+H4WPq8;PeG#)NtWi5>)p4MFuyX0dNOdR$jR8evOOo+ub>9 zcyi}*k*ot0Q~|K3XEJ4xUO-zi+ws^=eDWVm#DDh)AXoeXy=EXmh?(@9U8Go>A||<# ze2*f(Zw;B}zQi5KT5j0v=+Rqv(A;$;Xi&*5_N$7l6I~`p1k3Qzj zG=l{Fc+h-*Z|ZEbK8o>N+Qv(GMe40%9$&RW#^dOH1PAwg7bnJ`K8}(*EJzZBE~C#k z-z(?)Q29))zw`2;v8v-Y@^8Pfv~L6TSS0Ri#+{PHM|}GAkQ@tQw$Lto-PhvkaI9%y z^}&vglDEiX{m5Ql<}UWkvh*`a5H;Vq?mO7GC?@9+XrbuStVOp7-b3J43Tz_BDt0Aj zQ%gz)rg3ZM{}*6v7;eDw_2bfKWAMMz%vyL3!MJze29j@G@OeqWU! z|I$1XeLvZS`HL-Ly6UuG7LkIb6z8}1w2Z~885Z!`p%ttC=^x6@Xpj6rGK57kGh z@S(ueT|Nk@mQML)7ExApvESnxLh;eTrrv+& ze3vR58M$VjK*q+Jn?2?<@{X)%%kWo+DeLd0wGWvD^*1bNu)5Ps(icB3pd;WCr-)4Z zFCf!*XZ~pAKH$JNAJ1V0>#a)vd*4*nB=Tjj=_T*V;+N+;_}~`c>Sik4qB^%Ldba({ z_-uHgEc(j~=TvG>OmyihyuLKQ-fhL%!03}3@TYh+yy`eIA+C&|R3ctK>h}-U1?r82 zwx6M8mxtaYxxxwkBQKN+N!?N+8{ks1AVnqDV~X5|>?I-J(OfQ=jL9o~dapUul>AfzLbZRL=?G z#8oFfk+OVwwBabQH|I7bnt_|#6No7}l}mG@vXgt4@c8X>ltGVt0++q4)+NK~&hK#N z@&?uxBw(?1syPuy$-q=2xzuW|lfU+B3v{{H05FL3QPWukRd0?y^7FM(YSp`q<#-E< zgi;yYaxZ@0J21c2Hs2|*?rn!$nsAVNQqP$D_~-9q*KlOcw4Fa}B;6G;m8EYuD&;u>~$kSgfh+f1;q}J&Xut|j>wIFcxQCcIAx*_wlmvW;}ukpX($MWt>_NN$M$`$o;R6v8{!z)eMUQ(#Uz z@Q;x1sb&c#Q;5&F#ZZwsl$B)B1hP|k1f`KPD2Z*A;kg>;Tg8QHsXmUK!hA`B!!Z9- z*1!G-qNC47veK%jXCwtn`Mk*MbouRuAk5_tG!>8|GuxOV$^gt`?A;4|TVygA7K!US z>(>|xhPHkkD-@U#l7Yk0_+ORHY-blRe`Rf)^1GJ)m)NJATNzuqP5Ymy@gJkFXd^Q| z1raQkp8OwtFZWwrrGWZo{0&zuLM@3Ue@aT}*d0jP`G)!$LUVBybZNOb_@?SFpuKrM z?;%sa9?xdSA0Dy`n08Cafc6dSN^|0vdzy%olBM5}A9k4kUjN6kS3ge2nwsAGHaY=s z+p-^&cvbyF{`;5zM}j5#L!HKKVyXfy5T}iiD^!l_S8=grtxCc^R45debef<8;^wRJ zhpMJII$Xi;TK%*GCg|RVU5tsYcGd$==c7 zhrtr7m>_d^dk(RpNqG$V|E`NB1Dz?k3Z`!3qBn>?*)aAKw87QVWq#+W_p%wbv*?5< zedMGOSgD$QVXYApI&>;_hQS8N_ia%shR=LrVSfPw53{|{y05`Fk_xdrOMuD$*$#h( zjIgyIr2$)kmSgW{cA&F3=V`FZohKGc#YkpJK&__F`#J(JQMiI>VTb(}VDy0M6wci_=DCef%WzCo9))fBS@vSKpxTt9 zm`G%{@@R8JcJ`OtqmY?_)e!Zx+|rwz!7+hofPm`!>wZzWup%l7Y~KI0HS66%OWo3u zI#grjZNnH!IFACQujH9_TF5fytf@W?M_L63^%gR>FtQPu%ujHweCmEN#TuH0P&dXL zlex#&x}f2$9^fmk@5b|3?zjaud6>FHau~6-F(FcNy7jm)e7#(~OWHtilN9=CZT6hm z_I2K8p8i`3ZGH&W{6_fT2(9?>Yw=)RgqXvw?TXs_LL0Tn7F6EO)6KWY|rDnZc=SEY4`J z1>BeZx!uxencQUl`6{!A`^G1uornNprfJNFIoDJQYFd^nVmKhtKauHOIZsr(&$pH3 z{?p|Wf<2I&jkEe#AuUrmV@EjbPh;eP#|P2BSnt6+UidjbjBiRIFde~HkW=OgrxDq7 zMaz=JVtdT{`@D6V-;!K$Y5w%_GiAcSEktfwh^b&fci?S*xir{ABw~wPzQ>R@kW(je zON3!uXt=K{f5QF&u1_Pa&vAFq?hokn3VI~x+JK{q5g+H5V6R@nd{7?VB;IZPfv~)7 z4cB)Rf#(n-GcKw7H2qeXr)wyh=sLFb#{)0#2bS!i+;z)uG9w+C#xvfn#tGoH4;=Hv zY^jUsLV@#`+HqO^dh%KB~Cww=i|ll4(qZY zNvJ7$O9$#RD7J#a^4*=tI+3`HR$S_1|G-_uMO!b%yjZZXgFYGe1QMSnp6E9d(t;o@D%g(%EpWh_9yHJ7&Q>ygePBiy#Go0304zjjg;O8VXN2gjYH z)X1&I8NjawYp{YBwSTSJ=C(R*1G^#+x9ol`?R81$OEnSSL3F7wG(c@|MIO_GOKup5j7P9&OpMsThlXE+`bx_5t}fMqtj#{u z$0WYd1zHYEt=Fp4Nmk#1GZefJzL0}hSqsm16g-{ABC1S7b0nx=E&g#3ZFv{pDPHx(v5Z#k_-B6|p?2Z-DnD7V0QNWH5e zTN7YCVPvr?3U1FmRN%_KI$1gwB+qKSNzB>Qe z_KD>fGNtfYL-{I+e=p4}A_H;;Z{U_ghNuGuHXT=2wH#yK=yAGg`M5*6CWkAY z+03yFQ|ib`k8CxpE@fudii%$_S?l53HTHqock5#-kdiGZ(B^!u zE$Xf?bMCx9u}X|BdO7IHgrEh={@^RGmDLBdf}W*t_rm_v^f+v*?NY#JpddR|KiuTJ zpQU9er-LQcs5;;2N9YYTN=zfGK8BavGXoJ3@oUktn4~nbbN%RsXJV?aH5mV?ueXh8 zT-DYY_|f=ys3o0Eo4}F4?ip-B~XscB9t$!-uj|+DCS9Eg5 zct?%Kaa}H){&2Ca`jck=b?!LyPVljo(#2Mw8@W;n;@M*tLL)@;A~^0NtOz9tFOe6A z(0?KIdgLAJ6|X`#5N-@B2YtNc?Uk`*Pd**r@(HMfN>xV=9IgBP(7c!r?WH)8 z@A1gK9SB@^V|uZ!wWvG>=7fF}Z6ghMUZ7Z+yUC(%!i(Y%jm%nR-PC??xa`os7{tX= z6;x;mM)^oH-XLFxDtA>Esr4Ebqj)KJM)eCo*pH1pYi*ooej>e%>QF<5yyV^95xH zw7W2zbgr?;G|-z#0KqHdi(ne)vgfRf$ib6FC0e?y#%lp=oOf9&k=(@mkg}U%)pcoA z95jOKfu7{KYfY??PCD*05r&yKg=roMdk1wB>QNz$lE>z}rQkuLa#MO@B@n->fnHik z`!&hDmS2nuRwjL7?|_pG8x5{f-GZfuR8DClNJeou9E)m2{Jzgv+>{nMq$?%>=KORB z<@I8nzOj3yTelj<702>&!Y?#0lrsHxZz5O}xz(z?T!p(u=QGvLV1W!}g!k`F<;A}M ziOB?uc~f4z+s=K0n|E1cK2kdx3fS2g-^ZXmVFZF{y~8yZu#}=NF$?|vsN!o4WS`-H z_1DVQJ-STnk%d(GiP4k3KlCCX}{iZ>rjRWU^I*p zWvMU`%02iN(BesRBFsj(sXu@d(YGhG4#W}l zPg~8*h&?I>D~}##sUvjvJn-8x{3&VydHawl%6&7qNqGi$kIRDVw6snm8bs{sE^p)5 zT&2IBG1X7C(=FIt>94A|65K^5K}js%N>m@m+BiKk={q-^*j$_~`uw3$>V#%pLP*-Q zesDWeo9v9e-F7;7h*N46@`43r?G!IcDxQD=+KG={~0OqRnIPxlFWRQwJX+Y&S!ij?@DUvI@LxFWPfo>#;?o zty69q5EjGw?U(k}W#;t=pvA+YnM@;Il28^e?I~Gr+6{f=9js{O9VUj(mb@E^i9Q(l zj2{$xD%P8%nW#`!%E*E0cFae11Oydx`#1y}i1 zTfu}xDR4L3bTOWsp5d_{r+oJgTo(OJhEL zMs0Ja?92Kog8xuZP<<40+7y8^gztLKeb`lPJ;T9Hp+bc?SB9%;pAuos8~S_?`;%1@ zbmDSeXwAl*BPF6Y#WLCH@pi7`qmb(cW)#M{aNAOBOIVpn00=7hP;@ab{ZO#k5+U%Y zz9RK&NaXfG7AgxHeaeidl+0X7TriVm??0=%(bBWvi4_{I#qET=vtm~|30-Q$t)#)+ zg#u>{I4|`70)kn0ry$va(N_cOsh4dgGLiT+Ea(soVO$Zp@qv&PPMRKzPq^0iQMe;7 zC7bSfX@MjZbz1@n<%#!Z^K)&SUA#6tSx(=TM@_g3gcs>T3}jPwZ?MF;B-vka8z@;P z47WP9D!2i;HrCXbFowLuywx_k4W)p06>F;r;C>v?kPn%4KZvqMRNMdz&Pc;wOX=|HN}3F&WReka>51da5Dq5|T0@C1Z2WwIO~KiSsA=zQ9rd#Sx6Iq>3S<9I)etGmXAGa)TY@M{C$y*8 z($AvsdN`*hl@h1&72(`ZvlT|Kz^On9NJ8MT63=fY0B4q5b7_J55$LmQ1Lu*0WCOo7 zDR)4N$l!x6-?SGU{(Ff;viWh*cjs%+K1I&!S{q8aCky#%T|rWi^P`VK%mZ))p4n%v zDkGMrb2dGa2W7ClsUJv}Nu!MPgQtp$J|+Y+*7{un37t|ci`PBUlw!TZ`O*&$N`SqQj)+lRpA)b zgK-zU^4GSn4#$`c_9tcgqJ;%CDI7q2ZB9e?-FeJ0u{YevBK5eo5xF}Ay;I5QCVT1A z(M_Y_#Oq-;#8FGawP{z7f%>Y zb-0LNxS3O8z&E{98ZKP+^xrS9DLM)mByE34Hh?liEjeEeJ*#92!|FOfnV8N(a~!w~ z&$T-gAoJPT1$v<;pwP~rg^bARUYi)SBhW4A&V1^9)r04qAKMx9FMy)%(J)_PYF!*a zY;FXAEN1-__>v0rv)PR02#TkWVj3RR-4#|~6n-V{f4c`C9vbKeN!?3}(TE@^0t-j< z@P})7@_pyVUQD}fZCcV?co@K9WyiVfu6hvCEYik#s(l7d-wtcC?H2K7tXQ7WS*yCvV4V{qRv=LMGQv*I zY_{8`_ohc)HON-Ahx0?WmG8PP!0?SP=R=Z0jD4Yt+vIK6c~pP1W>geP%-s(DNplZAR`M@3$iV)-JfJBfn%G6M3;#E=aIXlGCd^*1x; z^5z*(ZXopv2_S$Yo}KO(FS462XV$3G&b}nm+z>ojPSMMnCM6oQ$s)%*Q#pCBt?i8QS6h28(6Jcnll2x}JQpHl>rC3+V4=v(@U|(2-!k>PtG@Z+{B7){_UZVRb zw(UPoacXWOFpS-e4y41eP2Zm`cyCdMX%dB)Ox6h|G#YYw^QT0Od?>weE?oUj|A2FJ zQ~dU1rDV{$*5sC@%xg-@A9s%3AV_R?%ur)-d12MY&;uwm8ee*yk)3P>Mvs-xpK1}x-q?mM-~J|_9M`HMA>|V*lj)GhiuZuK6#&dRZ&$+P)#9Ja zX_Kf+$hMx(!g?*{#oC9Iw8<*O$GQG2sJ+i09ZvxFqTijPFPPgO^bM+&wV?NBHCe+v z$)Zq3bhvuIk{=<(;PZ5#;ZQ=uGd}8FSmY18(?=rdXBOp$`Rc><3k~JSJD%TG_J5Rs zi0))F@>p|$cbOJ9J&jAa@3&}p4U?$mxZyY9^}cLR_f%lb#V2Pv73N*b`oJ{NH^QIb z4C12=ckV}rX`G7?!yTON!ztycH&)-op?&Z`icX~)C@}5JC)Ta!PUAs#D|>p#;tm~G z`WGT)FggUU{Uxj}0`D9uy?NI32@bh1q0Fw3@20#^Glb8*y%p8gNqCDgzO8(ezX)!C zL2wYn#NvKGCyj@6$cJm)Z1-2bI(wNU-pHqvPTi j6)>TNaVsyUrZx#qs;1F$7IH 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 K3tn^nDhRuVjhIr3pc>wsaQFFFp(i6 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 z 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 #GciClghc()^L!wLOT1 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 Xj6ffxl}ZX)T>dE7Ysn?U2DVM}Li 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 HKg<6I9Rss^ literal 0 HcmV?d00001 diff --git a/core-java-modules/core-java-io/src/main/resources/tiger.png b/core-java-modules/core-java-io/src/main/resources/tiger.png new file mode 100644 index 0000000000000000000000000000000000000000..97635d667769ceb67071851682d908d57b25f7bc GIT binary patch literal 46036 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 Date: Sun, 13 Feb 2022 21:09:41 +0800 Subject: [PATCH 005/105] =?UTF-8?q?feat:=20=E5=AD=97=E7=AC=A6=E5=9B=BE?= =?UTF-8?q?=E6=A1=88=EF=BC=8C=E6=88=91=E7=94=A8=E5=AD=97=E7=AC=A6=E7=94=BB?= =?UTF-8?q?=E4=B8=AA=E5=86=B0=E5=A2=A9=E5=A2=A9(https://www.wdbyte.com/jav?= =?UTF-8?q?a/char-image.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-io/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md index 07174d0..5d1fb00 100644 --- a/core-java-modules/core-java-io/README.md +++ b/core-java-modules/core-java-io/README.md @@ -1,4 +1,4 @@ -## core-java-performance-code +## core-java-io 当前模块包含 IO 相关代码 ### 相关文章 From ca34b424adbe9bb14e4ceb91939642e90b76be63 Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 16 Feb 2022 22:19:36 +0800 Subject: [PATCH 006/105] =?UTF-8?q?feat:[Java=20=E4=B8=AD=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=20String=20=E7=9A=84=20N=20=E7=A7=8D=E6=96=B9?= =?UTF-8?q?=E5=BC=8F](https://www.wdbyte.com/java/string-concat.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- core-java-modules/core-java-string/README.md | 6 ++ core-java-modules/core-java-string/pom.xml | 19 ++++ .../java/com/wdbyte/string/StringConcat.java | 88 +++++++++++++++++++ 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 core-java-modules/core-java-string/README.md create mode 100644 core-java-modules/core-java-string/pom.xml create mode 100644 core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java diff --git a/README.md b/README.md index 867abaf..d45dac2 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - 图 ## ⏳ Java 开发 - +- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) - [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/) - [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/) diff --git a/core-java-modules/core-java-string/README.md b/core-java-modules/core-java-string/README.md new file mode 100644 index 0000000..94dd352 --- /dev/null +++ b/core-java-modules/core-java-string/README.md @@ -0,0 +1,6 @@ +## core-java-io +当前模块包含 String 相关代码 + +### 相关文章 + +- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) \ No newline at end of file diff --git a/core-java-modules/core-java-string/pom.xml b/core-java-modules/core-java-string/pom.xml new file mode 100644 index 0000000..b6a5e1f --- /dev/null +++ b/core-java-modules/core-java-string/pom.xml @@ -0,0 +1,19 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + + core-java-string + + + 17 + 17 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java b/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java new file mode 100644 index 0000000..04b23de --- /dev/null +++ b/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java @@ -0,0 +1,88 @@ +package com.wdbyte.string; + +import java.util.Arrays; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +/** + * @author niulang + * @date 2022/02/16 + */ +public class StringConcat { + + public static void main(String[] args) { + System.out.println(concat()); + System.out.println(concat2()); + System.out.println(concat3()); + System.out.println(concat4()); + System.out.println(concat5()); + System.out.println(concat6()); + System.out.println(concat7()); + } + + public static String concat() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + String result = ""; + + for (String value : values) { + result = result + value; + } + return result; + } + + public static String concat2() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + String result = String.join("", values); + return result; + } + + public static String concat3() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + String result = ""; + for (String value : values) { + result = result + nullToString(value); + } + return result; + } + + public static String concat4() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + String result = ""; + for (String value : values) { + result = result.concat(nullToString(value)); + } + return result; + } + + public static String concat5() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + StringBuilder result = new StringBuilder(); + for (String value : values) { + result = result.append(nullToString(value)); + } + return result.toString(); + } + + public static String concat6() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + StringJoiner result = new StringJoiner(""); + for (String value : values) { + result = result.add(nullToString(value)); + } + return result.toString(); + } + + public static String concat7() { + String[] values = {"https", "://", "www.", "wdbyte", ".com", null}; + String result = Arrays.stream(values) + .filter(Objects::nonNull) + .collect(Collectors.joining()); + return result; + } + + public static String nullToString(String value) { + return value == null ? "" : value; + } + +} From 01443448aeabb1f9d5a3cf72b7944bb478cde4a8 Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 16 Feb 2022 22:21:39 +0800 Subject: [PATCH 007/105] =?UTF-8?q?feat:[Java=20=E4=B8=AD=E6=8B=BC?= =?UTF-8?q?=E6=8E=A5=20String=20=E7=9A=84=20N=20=E7=A7=8D=E6=96=B9?= =?UTF-8?q?=E5=BC=8F](https://www.wdbyte.com/java/string-concat.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/README.md | 3 ++- core-java-modules/pom.xml | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core-java-modules/README.md b/core-java-modules/README.md index 71d406b..caefe7a 100644 --- a/core-java-modules/README.md +++ b/core-java-modules/README.md @@ -31,4 +31,5 @@ - [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) - [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) -- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file +- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) +- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) \ No newline at end of file diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 66adcd7..7dfb70a 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -21,6 +21,10 @@ core-java-performance core-java-8 + core-java-9 + core-java-performance-code + core-java-io + core-java-string \ No newline at end of file From eb0a599c893c7f64184d8f6f92d1eb2c506fab58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Mon, 21 Feb 2022 13:40:30 +0800 Subject: [PATCH 008/105] docs: update comment --- core-java-modules/core-java-8/src/main/java/com/wdbyte/Dog.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8BiFunction.java | 2 +- .../src/main/java/com/wdbyte/Java8BiFunctionAndThen.java | 2 +- .../src/main/java/com/wdbyte/Java8BiFunctionAndThen2.java | 2 +- .../src/main/java/com/wdbyte/Java8BiFunctionFilter.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8Consumer.java | 2 +- .../src/main/java/com/wdbyte/Java8ConsumerAndThen.java | 2 +- .../src/main/java/com/wdbyte/Java8ConsumerForEach.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8ForEachArray.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8ForEachDiy.java | 2 +- .../src/main/java/com/wdbyte/Java8ForEachDiyConsumer.java | 2 +- .../src/main/java/com/wdbyte/Java8ForEachException.java | 2 +- .../src/main/java/com/wdbyte/Java8ForEachListNormal.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8ForEachMap.java | 2 +- .../src/main/java/com/wdbyte/Java8ForEachMapFilter.java | 2 +- .../src/main/java/com/wdbyte/Java8ForEachMapNormal.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8ForEachOrder.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8Function.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionAndThen.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionBiPredicate.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionBiPredicateOr.java | 2 +- .../main/java/com/wdbyte/Java8FunctionBiPredicateParams.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionLength.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionListToMap.java | 2 +- .../src/main/java/com/wdbyte/Java8FunctionString.java | 2 +- .../src/main/java/com/wdbyte/Java8ObjIntConsumer.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8PredicateAnd.java | 2 +- .../src/main/java/com/wdbyte/Java8PredicateChain.java | 2 +- .../src/main/java/com/wdbyte/Java8PredicateFilter.java | 2 +- .../src/main/java/com/wdbyte/Java8PredicateNeagete.java | 2 +- .../src/main/java/com/wdbyte/Java8PredicateObject.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8PredicateOr.java | 2 +- .../src/main/java/com/wdbyte/Java8PredicateTest.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8Supplier.java | 2 +- .../src/main/java/com/wdbyte/Java8SupplierFactory.java | 2 +- .../core-java-8/src/main/java/com/wdbyte/Java8SupplierInt.java | 2 +- .../src/main/java/com/wdbyte/Java8UnaryOperator.java | 2 +- .../src/main/java/com/wdbyte/Java8UnaryOperatorIdentify.java | 2 +- .../src/main/java/com/wdbyte/Java8UnaryOperatorParam.java | 2 +- .../src/main/java/com/wdbyte/Java8UnaryOperatorParams.java | 2 +- .../src/main/java/com/wdbyte/JavaBiFunctionFactory.java | 2 +- .../src/main/java/com/wdbyte/list_to_map/ListToMap.java | 2 +- .../java/com/wdbyte/list_to_map/ListToMapConcurrentHashMap.java | 2 +- .../src/main/java/com/wdbyte/list_to_map/ListToMapDog.java | 2 +- .../main/java/com/wdbyte/list_to_map/ListToMapDuplicateKey.java | 2 +- .../src/main/java/com/wdbyte/list_to_map/ListToMapSort.java | 2 +- .../src/main/java/com/wdbyte/StringInJdk.java | 2 +- .../src/main/java/com/wdbyte/string/StringConcat.java | 2 +- 48 files changed, 48 insertions(+), 48 deletions(-) diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Dog.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Dog.java index ca253a4..a3d4f8f 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Dog.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Dog.java @@ -6,7 +6,7 @@ import java.util.Base64; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/21 */ public class Dog { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunction.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunction.java index 788f9f5..ff762d2 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunction.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunction.java @@ -3,7 +3,7 @@ import java.util.function.BiFunction; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/26 */ public class Java8BiFunction { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen.java index c01bb4d..d240d3f 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen.java @@ -4,7 +4,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/26 */ public class Java8BiFunctionAndThen { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen2.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen2.java index 68c39f2..187ddbb 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen2.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionAndThen2.java @@ -4,7 +4,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/26 */ public class Java8BiFunctionAndThen2 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionFilter.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionFilter.java index 83e429f..3daa2a8 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionFilter.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8BiFunctionFilter.java @@ -6,7 +6,7 @@ import java.util.function.BiFunction; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/26 */ public class Java8BiFunctionFilter { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Consumer.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Consumer.java index 0402758..c03fed5 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Consumer.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Consumer.java @@ -3,7 +3,7 @@ import java.util.function.Consumer; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/20 */ public class Java8Consumer { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerAndThen.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerAndThen.java index 2055e05..c78da3a 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerAndThen.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerAndThen.java @@ -3,7 +3,7 @@ import java.util.function.Consumer; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/20 */ public class Java8ConsumerAndThen { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerForEach.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerForEach.java index 3dd6c7a..9d294ae 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerForEach.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ConsumerForEach.java @@ -5,7 +5,7 @@ import java.util.function.Consumer; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/20 */ public class Java8ConsumerForEach { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachArray.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachArray.java index 70f95f1..6647081 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachArray.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachArray.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachArray { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiy.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiy.java index 635654c..e8063fe 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiy.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiy.java @@ -5,7 +5,7 @@ import java.util.function.Consumer; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/25 */ public class Java8ForEachDiy { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiyConsumer.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiyConsumer.java index 4d7927e..2265b05 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiyConsumer.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachDiyConsumer.java @@ -6,7 +6,7 @@ import java.util.stream.Stream; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachDiyConsumer { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachException.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachException.java index 6fff108..4a0f6ef 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachException.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachException.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachException { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachListNormal.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachListNormal.java index 37c2fd3..90aaf8a 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachListNormal.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachListNormal.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachListNormal { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMap.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMap.java index ffc95e1..dd44b34 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMap.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMap.java @@ -4,7 +4,7 @@ import java.util.Map.Entry; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachMap { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapFilter.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapFilter.java index fbc1cf1..e29210c 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapFilter.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapFilter.java @@ -3,7 +3,7 @@ import java.util.HashMap; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachMapFilter { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapNormal.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapNormal.java index 306e0bb..2af0c47 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapNormal.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachMapNormal.java @@ -6,7 +6,7 @@ import java.util.Map.Entry; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachMapNormal { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachOrder.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachOrder.java index b809a5c..152a248 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachOrder.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ForEachOrder.java @@ -5,7 +5,7 @@ import java.util.stream.Stream; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/18 */ public class Java8ForEachOrder { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Function.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Function.java index 7fd4dff..c44a097 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Function.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Function.java @@ -3,7 +3,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @website https://www.wdbyte.com * @date 2021/07/17 */ diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionAndThen.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionAndThen.java index 2ae4845..1d7eb31 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionAndThen.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionAndThen.java @@ -3,7 +3,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/17 */ public class Java8FunctionAndThen { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicate.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicate.java index c258741..8907e02 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicate.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicate.java @@ -3,7 +3,7 @@ import java.util.function.BiPredicate; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/01 */ public class Java8FunctionBiPredicate { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateOr.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateOr.java index 3bd5f9f..55dbb95 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateOr.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateOr.java @@ -3,7 +3,7 @@ import java.util.function.BiPredicate; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/01 */ public class Java8FunctionBiPredicateOr { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateParams.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateParams.java index 8f94722..43c756d 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateParams.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionBiPredicateParams.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/01 */ public class Java8FunctionBiPredicateParams { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionLength.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionLength.java index 8b7ec4d..4116921 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionLength.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionLength.java @@ -3,7 +3,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/17 */ public class Java8FunctionLength { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionListToMap.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionListToMap.java index 660de89..c77d742 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionListToMap.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionListToMap.java @@ -7,7 +7,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/17 */ public class Java8FunctionListToMap { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionString.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionString.java index 02a83a2..4a9ca5c 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionString.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8FunctionString.java @@ -6,7 +6,7 @@ import java.util.function.Function; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/17 */ public class Java8FunctionString { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ObjIntConsumer.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ObjIntConsumer.java index 28499fb..fdb8e0d 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ObjIntConsumer.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8ObjIntConsumer.java @@ -6,7 +6,7 @@ import java.util.function.ObjIntConsumer; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/20 */ public class Java8ObjIntConsumer { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateAnd.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateAnd.java index 440afa6..9824bfb 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateAnd.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateAnd.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateAnd { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateChain.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateChain.java index ba7fda5..d32ca96 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateChain.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateChain.java @@ -6,7 +6,7 @@ import java.util.function.Predicate; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateChain { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateFilter.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateFilter.java index d386c8e..a1529b8 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateFilter.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateFilter.java @@ -5,7 +5,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateFilter { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateNeagete.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateNeagete.java index 3363538..8a7e4bc 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateNeagete.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateNeagete.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateNeagete { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateObject.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateObject.java index d7e10cd..26303df 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateObject.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateObject.java @@ -5,7 +5,7 @@ import java.util.function.Predicate; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateObject { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateOr.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateOr.java index e1e2e4a..1cb71c8 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateOr.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateOr.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateOr { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateTest.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateTest.java index 5f3472f..0df70fa 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateTest.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8PredicateTest.java @@ -3,7 +3,7 @@ import java.util.function.Predicate; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/19 */ public class Java8PredicateTest { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Supplier.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Supplier.java index 33c6607..7e9edb5 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Supplier.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8Supplier.java @@ -6,7 +6,7 @@ import java.util.function.Supplier; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/21 */ public class Java8Supplier { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierFactory.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierFactory.java index fa1d00d..1a2fb80 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierFactory.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierFactory.java @@ -3,7 +3,7 @@ import java.util.function.Supplier; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/21 */ public class Java8SupplierFactory { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierInt.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierInt.java index f0a14e7..cc3d14f 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierInt.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8SupplierInt.java @@ -4,7 +4,7 @@ import java.util.function.IntSupplier; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class Java8SupplierInt { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperator.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperator.java index 8711559..ddf91b6 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperator.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperator.java @@ -5,7 +5,7 @@ import java.util.function.UnaryOperator; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/02 */ public class Java8UnaryOperator { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorIdentify.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorIdentify.java index ba719eb..5085de4 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorIdentify.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorIdentify.java @@ -8,7 +8,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/02 */ public class Java8UnaryOperatorIdentify { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParam.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParam.java index f7c048b..d2bfb7a 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParam.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParam.java @@ -9,7 +9,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/02 */ public class Java8UnaryOperatorParam { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParams.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParams.java index dd1d3f0..4e81857 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParams.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Java8UnaryOperatorParams.java @@ -6,7 +6,7 @@ import java.util.function.UnaryOperator; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/08/02 */ public class Java8UnaryOperatorParams { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/JavaBiFunctionFactory.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/JavaBiFunctionFactory.java index 871dd16..241c5a4 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/JavaBiFunctionFactory.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/JavaBiFunctionFactory.java @@ -3,7 +3,7 @@ import java.util.function.BiFunction; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/26 */ public class JavaBiFunctionFactory { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMap.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMap.java index 5c8ddf4..8e29127 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMap.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMap.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class ListToMap { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapConcurrentHashMap.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapConcurrentHashMap.java index acb0ad9..a5bf22e 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapConcurrentHashMap.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapConcurrentHashMap.java @@ -7,7 +7,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class ListToMapConcurrentHashMap { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDog.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDog.java index 0647f64..e50fa40 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDog.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDog.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class ListToMapDog { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDuplicateKey.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDuplicateKey.java index 678b8c4..898dd62 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDuplicateKey.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapDuplicateKey.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class ListToMapDuplicateKey { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapSort.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapSort.java index 5367255..c8b95af 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapSort.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/list_to_map/ListToMapSort.java @@ -7,7 +7,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/07/23 */ public class ListToMapSort { diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java index ee0f6c1..97e754b 100644 --- a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/StringInJdk.java @@ -16,7 +16,7 @@ import org.openjdk.jmh.infra.Blackhole; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2021/12/23 */ @State(Scope.Benchmark) diff --git a/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java b/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java index 04b23de..93ff077 100644 --- a/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java +++ b/core-java-modules/core-java-string/src/main/java/com/wdbyte/string/StringConcat.java @@ -6,7 +6,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/02/16 */ public class StringConcat { From 1a65e5fbe4926bab4ffc15f87b7fc54f47bb2f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Mon, 21 Feb 2022 13:41:32 +0800 Subject: [PATCH 009/105] delete --- docs/computer/protocol-http.md | 223 ----- docs/desgin-pattern/pattern-singleton.md | 210 ----- docs/develop/web-base-01.md | 202 ---- docs/develop/web-base-02.md | 189 ---- docs/develop/web-base-03.md | 348 ------- docs/develop/web-base-04.md | 295 ------ docs/develop/web-base-05.md | 177 ---- docs/elasticsearch/elasticsearch-base-curd.md | 814 ---------------- docs/elasticsearch/elasticsearch-plug-head.md | 83 -- docs/elasticsearch/elasticsearch-query.md | 288 ------ docs/elasticsearch/solr-install.md | 204 ----- docs/htmlcss/css-mac-window.md | 95 -- .../htmlcss/jquery-easing-anchor-animation.md | 136 --- docs/java/java-load-balancing.md | 280 ------ docs/java/java-think-code-standards.md | 159 ---- docs/jdk/java-07-feature.md | 462 ---------- docs/jdk/java-08-feature-lambda.md | 426 --------- docs/jdk/java-08-feature-optional.md | 357 -------- docs/jdk/java-08-feature-stream.md | 601 ------------ docs/jdk/java-08-feature-time.md | 305 ------ docs/jdk/java-09-feature.md | 475 ---------- docs/jdk/java-10-feature.md | 217 ----- docs/jdk/java-11-feature.md | 194 ---- docs/jdk/java-src-arrayList-linkedList.md | 475 ---------- docs/jdk/java-src-concurrent-hashmap.md | 603 ------------ docs/jdk/java-src-hashmap.md | 488 ---------- docs/jvm/jvm-hotput.md | 416 --------- docs/linux/linux-crontab.md | 140 --- docs/linux/linux-install-nginx.md | 133 --- docs/linux/linux-manjaro.md | 216 ----- docs/linux/linux-ubuntu-start.md | 352 ------- docs/mq/io-blocking .md | 363 -------- docs/mq/io-bon-blocking.md | 311 ------- docs/mq/io-nio-selector.md | 408 --------- docs/mq/mq-activemq.md | 605 ------------ docs/mq/mq-introduction.md | 93 -- docs/mq/mq-kafka-introduction.md | 337 ------- docs/springboot/springboot-01-quick-start.md | 317 ------- docs/springboot/springboot-02-config.md | 499 ---------- docs/springboot/springboot-03-auto-config.md | 263 ------ docs/springboot/springboot-04-log.md | 225 ----- .../springboot-05-web-static-template.md | 370 -------- .../springboot-06-web-filter-apo-webbase.md | 536 ----------- .../springboot/springboot-07-web-exception.md | 378 -------- docs/springboot/springboot-08-banner.md | 277 ------ docs/springboot/springboot-09-data-jdbc.md | 233 ----- docs/springboot/springboot-10-data-jpa.md | 343 ------- docs/springboot/springboot-11-data-mybatis.md | 370 -------- .../springboot-12-data-mybatis-page.md | 500 ---------- docs/springboot/springboot-13-email.md | 372 -------- docs/springboot/springboot-14-https.md | 237 ----- docs/springboot/springboot-15-my-starter.md | 413 --------- docs/springboot/springboot-16-web-swagger.md | 451 --------- docs/springboot/springboot-17-admin.md | 489 ---------- docs/springboot/springboot-18-module.md | 472 ---------- docs/tool/tool-Lombok.md | 137 --- docs/tool/tool-apache-ant.md | 274 ------ docs/tool/tool-arthas.md | 865 ------------------ docs/tool/tool-async-profiler.md | 300 ------ docs/tool/tool-curl.md | 233 ----- docs/tool/tool-idea-skill.md | 176 ---- .../tool/tool-install-tomcat-many-instance.md | 157 ---- docs/tool/tool-java-download.md | 210 ----- docs/tool/tool-jmh.md | 385 -------- docs/tool/tool-mybatis-generator.md | 132 --- 65 files changed, 21294 deletions(-) delete mode 100644 docs/computer/protocol-http.md delete mode 100644 docs/desgin-pattern/pattern-singleton.md delete mode 100644 docs/develop/web-base-01.md delete mode 100644 docs/develop/web-base-02.md delete mode 100644 docs/develop/web-base-03.md delete mode 100644 docs/develop/web-base-04.md delete mode 100644 docs/develop/web-base-05.md delete mode 100644 docs/elasticsearch/elasticsearch-base-curd.md delete mode 100644 docs/elasticsearch/elasticsearch-plug-head.md delete mode 100644 docs/elasticsearch/elasticsearch-query.md delete mode 100644 docs/elasticsearch/solr-install.md delete mode 100644 docs/htmlcss/css-mac-window.md delete mode 100644 docs/htmlcss/jquery-easing-anchor-animation.md delete mode 100644 docs/java/java-load-balancing.md delete mode 100644 docs/java/java-think-code-standards.md delete mode 100644 docs/jdk/java-07-feature.md delete mode 100644 docs/jdk/java-08-feature-lambda.md delete mode 100644 docs/jdk/java-08-feature-optional.md delete mode 100644 docs/jdk/java-08-feature-stream.md delete mode 100644 docs/jdk/java-08-feature-time.md delete mode 100644 docs/jdk/java-09-feature.md delete mode 100644 docs/jdk/java-10-feature.md delete mode 100644 docs/jdk/java-11-feature.md delete mode 100644 docs/jdk/java-src-arrayList-linkedList.md delete mode 100644 docs/jdk/java-src-concurrent-hashmap.md delete mode 100644 docs/jdk/java-src-hashmap.md delete mode 100644 docs/jvm/jvm-hotput.md delete mode 100644 docs/linux/linux-crontab.md delete mode 100644 docs/linux/linux-install-nginx.md delete mode 100644 docs/linux/linux-manjaro.md delete mode 100644 docs/linux/linux-ubuntu-start.md delete mode 100644 docs/mq/io-blocking .md delete mode 100644 docs/mq/io-bon-blocking.md delete mode 100644 docs/mq/io-nio-selector.md delete mode 100644 docs/mq/mq-activemq.md delete mode 100644 docs/mq/mq-introduction.md delete mode 100644 docs/mq/mq-kafka-introduction.md delete mode 100644 docs/springboot/springboot-01-quick-start.md delete mode 100644 docs/springboot/springboot-02-config.md delete mode 100644 docs/springboot/springboot-03-auto-config.md delete mode 100644 docs/springboot/springboot-04-log.md delete mode 100644 docs/springboot/springboot-05-web-static-template.md delete mode 100644 docs/springboot/springboot-06-web-filter-apo-webbase.md delete mode 100644 docs/springboot/springboot-07-web-exception.md delete mode 100644 docs/springboot/springboot-08-banner.md delete mode 100644 docs/springboot/springboot-09-data-jdbc.md delete mode 100644 docs/springboot/springboot-10-data-jpa.md delete mode 100644 docs/springboot/springboot-11-data-mybatis.md delete mode 100644 docs/springboot/springboot-12-data-mybatis-page.md delete mode 100644 docs/springboot/springboot-13-email.md delete mode 100644 docs/springboot/springboot-14-https.md delete mode 100644 docs/springboot/springboot-15-my-starter.md delete mode 100644 docs/springboot/springboot-16-web-swagger.md delete mode 100644 docs/springboot/springboot-17-admin.md delete mode 100644 docs/springboot/springboot-18-module.md delete mode 100644 docs/tool/tool-Lombok.md delete mode 100644 docs/tool/tool-apache-ant.md delete mode 100644 docs/tool/tool-arthas.md delete mode 100644 docs/tool/tool-async-profiler.md delete mode 100644 docs/tool/tool-curl.md delete mode 100644 docs/tool/tool-idea-skill.md delete mode 100644 docs/tool/tool-install-tomcat-many-instance.md delete mode 100644 docs/tool/tool-java-download.md delete mode 100644 docs/tool/tool-jmh.md delete mode 100644 docs/tool/tool-mybatis-generator.md diff --git a/docs/computer/protocol-http.md b/docs/computer/protocol-http.md deleted file mode 100644 index dc47d7d..0000000 --- a/docs/computer/protocol-http.md +++ /dev/null @@ -1,223 +0,0 @@ ---- -title: 网络协议之HTTP -date: 2018-07-14 00:37:19 -updated: 2018-07-14 00:37:19 -url: computer/protocol-http -categories: -- 计算机 -tags: -- 协议 -- HTTP ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![HTTP](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/http-to-https-Image-1024x576.png) - -### HTTP的简介 - -超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络协议。所有的WWW文件都必须遵守这个标准。 - - - - ![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5008f0a9cb84c78ffc040ef0464d10df.jpg) - -HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。 - -HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。 - -HTTP协议工作于客户端-服务端架构为上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。 - -### HTTP的特点 -1. 支持客户/服务器模式。 -1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。 -由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。 -1. 灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。 -1. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 -1. 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。 - -### HTTP的URL(UniformResourceLocator) -URL格式: - ``` - http://host[:port][abs_path] -``` - -`http`表示要通过HTTP协议来定位网络资源。 -`host`表示合法的Internet主机域名或IP地址(以点分十进制格式表示)。 -`port`用于指定一个端口号,拥有被请求资源的服务器主机监听该端口的TCP连接。 -如果port是空,则使用`缺省的端口80`。当服务器的端口不是80的时候,需要显式指定端口号。 -`abs_path`指定请求资源的URI(Uniform Resource Identifier,统一资源定位符),如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出。通常这个工作浏览器就帮我们完成了。 - -### HTTP的请求消息Request -消息格式: - ![HTTP 消息格式](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ae3fe2acba5a78462d247d03cccaa3ec.jpg) - -先来看下HTTP的请求消息头: -``` - - Accept: text/html,image/* 【浏览器告诉服务器,它支持的数据类型】 - - Accept-Charset: ISO-8859-1 【浏览器告诉服务器,它支持哪种字符集】 - - Accept-Encoding: gzip,compress 【浏览器告诉服务器,它支持的压缩格式】 - - Accept-Language: en-us,zh-cn 【浏览器告诉服务器,它的语言环境】 - - Host: www.codingme.net:80【浏览器告诉服务器,它的想访问哪台主机】 - - If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT【浏览器告诉服务器,缓存数据的时间】 - - Referer: http://www.codingme.net/index.jsp【浏览器告诉服务器,客户机是从那个页面来的—反盗链】 - - 8.User-Agent: Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 5.0)【浏览器告诉服务器,浏览器的内核是什么】 - - Cookie【浏览器告诉服务器,带来的Cookie是什么】 - - Connection: close/Keep-Alive 【浏览器告诉服务器,请求完后是断开链接还是保持链接】 - - Date: Tue, 11 Jul 2000 18:23:51 GMT【浏览器告诉服务器,请求的时间 -``` -使用Chrome请求网址进行观察: - ![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6e442fc98cc8988ef631f7103f213b0b.jpg) -Chrome请求信息 - -``` -Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 -Accept-Encoding:gzip, deflate -Accept-Language:zh-CN,zh;q=0.8 -Connection:keep-alive -Cookie:JSESSIONID=2A40A51EDDE663C840A2D03B7587D660; Hm_lvt_1b51c3ea9a3e7b1a2bc55df97ab4efd3=1500964170,1500976171,1500994669,1501056813; Hm_lpvt_1b51c3ea9a3e7b1a2bc55df97ab4efd3=1501056816 -Host:blog.codingme.net -Upgrade-Insecure-Requests:1 -User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3080.5 Safari/537.36 -``` -### HTTP的响应消息Response -一个完整的HTTP响应应该包含四个部分: - -1. 一个状态行【用于描述服务器对请求的处理结果。】 -1. 多个消息头【用于描述服务器的基本信息,以及数据的描述,服务器通过这些数据的描述信息,可以通知客户端如何处理等一会儿它回送的数据】 -1. 一个空行 -1. 实体内容【服务器向客户端回送的数据】 - -响应消息头的详细解释: - - - Location: http://www.codingme.net/index.jsp 【服务器告诉浏览器要跳转到哪个页面】 - - Server:apache tomcat【服务器告诉浏览器,服务器的型号是什么】 - - Content-Encoding: gzip 【服务器告诉浏览器数据压缩的格式】 - - Content-Length: 80 【服务器告诉浏览器回送数据的长度】 - - Content-Language: zh-cn 【服务器告诉浏览器,服务器的语言环境】 - - Content-Type: text/html; charset=GB2312 【服务器告诉浏览器,回送数据的类型】 - - Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT【服务器告诉浏览器该资源上次更新时间】 - - Refresh: 1;url=http://www.codingme.net【服务器告诉浏览器要定时刷新】 - - Content-Disposition: attachment; filename=aaa.zip【服务器告诉浏览器以下载方式打开数据】 - - Transfer-Encoding: chunked 【服务器告诉浏览器数据以分块方式回送】 - - Set-Cookie:SS=Q0=5Lb_nQ; path=/search【服务器告诉浏览器要保存Cookie】 - - Expires: -1【服务器告诉浏览器不要设置缓存】 - - Cache-Control: no-cache 【服务器告诉浏览器不要设置缓存】 - - Pragma: no-cache 【服务器告诉浏览器不要设置缓存】 - - Connection: close/Keep-Alive 【服务器告诉浏览器连接方式】 - - Date: Tue, 11 Jul 2000 18:23:51 GMT【服务器告诉浏览器回送数据的时间】 - -Chrome请求网址http://bing.codingme.net 抓包展示; - ![Chrome 请求信息](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/09766111948104bd7ac918219cf7eedf.jpg) - - -### HTTP的状态码 -状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别: - -1xx:指示信息--表示请求已接收,继续处理 - -2xx:成功--表示请求已被成功接收、理解、接受 - -3xx:重定向--要完成请求必须进行更进一步的操作 - -4xx:客户端错误--请求有语法错误或请求无法实现 - -5xx:服务器端错误--服务器未能实现合法的请求 - -常见状态码: - -- 200 OK //客户端请求成功 -- 400 Bad Request //客户端请求有语法错误,不能被服务器所理解 -- 401 Unauthorized //请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用 -- 403 Forbidden //服务器收到请求,但是拒绝提供服务 -- 404 Not Found //请求资源不存在,eg:输入了错误的URL -- 500 Internal Server Error //服务器发生不可预期的错误 -- 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常 - -详情可以查看[更多状态码](http://kb.cnblogs.com/page/168720/) - - -### HTTP的请求方法(动作) -HTTP协议中定义了8种方法来表明不同的动作 -1. OPTIONS - 返回服务器针对特定资源所支持的HTTP请求方法,也可以利用向web服务器发送‘*’的请求来测试服务器的功能性。 - -1. HEAD - 向服务器索与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以再不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。 - -1. GET - 向特定的资源发出请求。注意:GET方法不应当被用于产生“副作用”的操作中,例如在Web Application中,其中一个原因是GET可能会被网络蜘蛛等随意访问。 - Loadrunner中对应get请求函数:web_link和web_url. - -1. POST - 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 Loadrunner中对应POST请求函数:web_submit_data,web_submit_form. - -1. PUT - 向指定资源位置上传其最新内容。 - -1. DELETE - 请求服务器删除Request-URL所标识的资源。 - -1. TRACE - 回显服务器收到的请求,主要用于测试或诊断。 - -1. CONNECT -HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 - -### TCP的三次握手 -HTTP使用TCP进行输出传输,在建立TCP连接时会进行三次握手。 - -所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示: - - ![TCP 三次握手](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7026e1d3c06ff8407683f0447073e406.jpg) - -1. 建立连接时,客户端发送SYN包(SYN=i)到服务器,并进入到SYN-SEND状态,等待服务器确认 -1. 服务器收到SYN包,必须确认客户的SYN(ack=i+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器进入SYN-RECV状态 -1. 客户端收到服务器的SYN+ACK包,向服务器发送确认报ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手,客户端与服务器开始传送数据。 - -### HTTP协议工作流程 -以访问网站为例,在回车之后所发生的动作: - -1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址; -1. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立TCP连接; -1. 浏览器发出读取文件(URL 中域名后面部分对应的文件)的HTTP 请求,该请求报文作为 TCP 三次握手的第三个报文的数据发送给服务器; -1. 服务器对浏览器请求作出响应,并把对应的 html 文本发送给浏览器; -1. 释放 TCP连接; -1. 浏览器将该 html 文本并显示内容;   - -### GET请求和POST请求的区别 -使用Chrome浏览器进行GET请求测试: ->Request URL:http://localhost:8888/01-web_servlet/loginServlet?username=zxy&password=123 -Request Method:GET -Status Code:200 -Remote Address:[::1]:8888 -Referrer Policy:no-referrer-when-downgrade - -使用Chrome浏览器进行POST请求测试: ->Request URL:http://localhost:8888/01-web_servlet/loginServlet -Request Method:POST -Status Code:200 -Remote Address:[::1]:8888 -Referrer Policy:no-referrer-when-downgrade - -由测试可以看到GET和POST最明显的区别就是 -1. GET携带的参数传递置于URL中以?分割URL和传输数据,多个参数用&连接,如果数据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,`不安全`, -POST把提交的数据放置在是HTTP包的包体中。因此,GET提交的数据会在地址栏中显示出来,而`POST`提交,地址栏`不会改变` -1. 网上搜索得到如下区别 -1. GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制. -1. GET方式需要使用Request.QueryString来取得变量的值,而POST方式通过Request.Form来获取变量的值。 -1. GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该用户的账号和密码. -1. GET产生一个TCP数据包;POST产生两个TCP数据包。 - - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) - diff --git a/docs/desgin-pattern/pattern-singleton.md b/docs/desgin-pattern/pattern-singleton.md deleted file mode 100644 index 5a4cabc..0000000 --- a/docs/desgin-pattern/pattern-singleton.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -title: 设计模式 -创建型模式之单例模式的五种实现 -date: 2018-03-08 16:03:28 -url: dp/pattern-singleton -updated: 2018-03-08 16:03:28 -categories: -- 设计模式 -tags: -- 设计模式 -- 单例模式 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -# 单例模式(Singleton) - -单例模式是在 `GOF`的23种设计模式里较为简单的一种,下面引用百度百科介绍: ->单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例 - - - -许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。 - -在Java中,确保一个类只有一个对象实例可以通过权限的修饰来实现。 -# 单例模式 - 饿汉模式 -单例模式的饿汉模式指全局的单例实例在第一次被使用时构建。 -具体实现: -``` - // 单例模式的饿汉模式实现 - public class Singleton { - private final static Singleton SINGLETON= new Singleton(); - // Private constructor suppresses - private Singleton() {} - - // default public constructor - public static Singleton getInstance() { - return SINGLETON; - } - } -``` -在饿汉模式实现方式中,程序的主要特点是: -1. 私有构造方法 -2. 私有静态属性,维护自身实例 -3. 静态服务方法,获取实例 -4. 初始化时候创建,消耗初始化系统资源 - -# 单例模式 - 懒汉模式 - 普通 - -懒汉模式,也是最常用的形式,饿汉模式让程序在初始化时候进行加载,有时为了节约资源,我们需要在需要的时候进行加载,这时候我们可以使用懒汉模式。 -具体实现: -``` -public class SingletonLayload { - // 私有化自身类对象 - private static SingletonLayload SINGLETON; - // 私有化构造方法 - private SingletonLayload() {} - - // 静态方法获取实例 - public static SingletonLayload getInstance() { - if(SINGLETON== null ) { - SINGLETON= new SingletonLayload(); - } - return SINGLETON; - } -} -``` -# 单例模式 - 懒汉模式 - 同步锁 -在多线程的环境中,简单的单例模式将会出现问题,试想在上面的懒汉模式中,如果多线程并发执行`getInstance()`,当线程A执行到: ->`INSTANCE = new SingletonLayload();` - -却还没有执行完毕时,线程B执行到`if(INSTANCE == null )`,此时就无法保证单例特性。 -因此在多线程环境中,单例模式需要使用同步锁确保实现真正的单例。 -具体实现: -``` -public class SingletonLayloadSyn { - // 私有化自身类对象 - private static SingletonLayloadSyn SINGLETON; - // 私有化构造方法 - private SingletonLayloadSyn() {} - // 静态方法获取实例 - public static synchronized SingletonLayloadSyn getInstance() { - if(SINGLETON == null ) { - SINGLETON = new SingletonLayloadSyn(); - } - return SINGLETON; - } - -} -``` - 通过在`getInstance()`方法上添加 `synchronized` 关键字可以解决多线程带来的问题。 - -# 单例模式 - 懒汉模式 - 双重校验锁 -使用上面的( 多线程下 - 懒汉模式 - 同步锁)方式在解决多线程问题时虽然可以达到确保线程安全的目的,但是使用了`synchronized `关键字之后在需要多次调用时,会让代码的执行效率大大降低。那么有没有在确保线程安全的同时又可以兼顾效率的方法呢? -具体实现: - -``` -public class SingletonLayLoadSynDCL { - // 私有化自身类对象 - private static SingletonLayLoadSynDCL SINGLETON; - // 私有化构造方法 - private SingletonLayLoadSynDCL() { - } - - public static SingletonLayLoadSynDCL getInstance() { - if (SINGLETON == null) { - synchronized(SingletonLayLoadSynDCL.class) { - SINGLETON = new SingletonLayLoadSynDCL(); - } - } - return SINGLETON; - } -} -``` -使用 `synchronized` 确保线程安全,在SINGLETON 为 `null` 时才进行创建实例,但是仍然不能 保证在实例未创建完成时候有新的线程执行到 `if (SINGLETON == null)`;因此,仍然不够安全。 -修改 `getInstance()`方法。 -具体实现: - -``` -public class SingletonLayLoadSynDCL { - // 私有化自身类对象 - private static SingletonLayLoadSynDCL SINGLETON; - // 私有化构造方法 - private SingletonLayLoadSynDCL() { - } - - // 使用双重校验锁确保线程安全的同时兼顾执行效率 - public static SingletonLayLoadSynDCL getInstance() { - if (SINGLETON == null) { // 第一重检查 - synchronized (SingletonLayLoadSynDCL.class) { - if (SINGLETON == null) { //第二重检查 - SINGLETON = new SingletonLayLoadSynDCL(); - } - } - } - return SINGLETON; - - } -} -``` -看似完美的双检查模式,在理论上是没有问题的。但是在实际的情况里,有可能发生在没有构造完毕的情况下SINGLETON 引用已经不是 NULL 的情况,这时候如果有其他线程执行到`if (SINGLETON == null) { // 第一重检查`则会获取到一个不正确的 SINGLETON 引用。这是由于`JVM` 的无序写入引起的。 - -幸好,在 `JDK1.5` 之后,提供了`volatile`关键字,用于确保被修饰的变量的读写不允许被控制。因此修改上面具体实现为: -``` -/** - *

- * 使用双重校验锁以及volatile关键字确保线程安全的同时兼顾执行效率 - * @author niujinpeng - */ -public class SingletonLayLoadSynDCL { - // 私有化自身类对象 - // private static SingletonLayLoadSynDCL SINGLETON; - private volatile static SingletonLayLoadSynDCL SINGLETON; - // 私有化构造方法 - private SingletonLayLoadSynDCL() {} - - // 使用双重校验锁确保线程安全的同时兼顾执行效率 - public static SingletonLayLoadSynDCL getInstance() { - if (SINGLETON == null) { - synchronized (SingletonLayLoadSynDCL.class) { - if (SINGLETON == null) { - SINGLETON = new SingletonLayLoadSynDCL(); - } - } - } - return SINGLETON; - - } -} -``` - -# 单例模式 - 懒汉模式 - 内部类 -除了使用上面的懒汉模式实现方式之外,在解决多线程问题中,《Effective Java》的作者给出了另外一种保证线程安全且兼顾效率的方式,利用了静态内部类以及类加载特性实现。静态内部类只有在调用时才会加载,而静态属性随着类的加载而加载,类的加载初始化只会有一次。因此保证了获取实例的唯一性。 -具体实现: -``` -package cn.snowflow.pattern.singleton; -/** - *

- * 利用静态内部类实现线程安全且兼顾效率的单例模式 - * @author niujinpeng - */ -public class SingletonLayloadSynSafe { - //静态内部类 - public static class SingletonHolder{ - static final SingletonLayloadSynSafe INSTANCE = - new SingletonLayloadSynSafe(); - } - // 私有化构造方法 - private SingletonLayloadSynSafe() {} - - // 公有方法获取实例 - public static SingletonLayloadSynSafe getInstance() { - return SingletonHolder.INSTANCE; - } - -} - -``` - -如果使用单例模式-饿汉模式,推荐`【单例模式 - 饿汉模式】` -如果使用单例模式-懒汉模式,推荐`【单例模式 - 懒汉模式 - 内部类 】` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/develop/web-base-01.md b/docs/develop/web-base-01.md deleted file mode 100644 index c80b540..0000000 --- a/docs/develop/web-base-01.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -title: Web笔记(一) Web 简介与开发环境搭建 -date: 2018-08-15 05:03:59 -url: develop/web/web/base-01 -updated: 2018-08-15 05:03:59 -categories: - - Java 开发 -tags: - - Tomcat - - Java EE ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -### Web应用程序的工作原理 -大多数的Web应用程序结构都是采用最为流行的B/S软件开发体系结构,将Web应用程序部署在Web服务器上,只要Web服务器启动,用户就可以通过客户端浏览器发送[HTTP](http://www.codingme.net/post/java-web-01)请求到Web服务器,此时运行在Web服务器上对应的Web应用程序将处理客户端请求,处理完成后做出响应。 - -Web应用程序工作原理图如下: - -![Web应用程序工作原理](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/2a80764098b039c156a354df120d12f9.jpg) - - -### Web应用开发技术 -Web应用开发技术又分为客户端开发技术,服务器开发技术。 -客户端开发技术: - -1. HTML -1. CSS -1. JavaScript -... - -服务器开发技术: -1. PHP -1. ASP.NET -1. Servlet -1. JSP -... - -两个概念: -`静态页面`:类似于HTML这种单纯的客户端页面,在每次访问的时候得到的信息都是相同的吗吗,和后台没有任何交互,它是实际存在的,无需经过服务器的编译,直接加载到客户浏览器上显示出来。我们称之为静态页面。 - -`动态页面`:相对静态页面,显示的内容可以随着时间、环境或者数据库操作的结果而发生改变的。我们称之为动态页面。 - -### Web 服务器 -进行Java Web开发环境的搭建,首先我们需要了解下Web服务器。 -WEB服务器也称为WWW(WORLD WIDE WEB)服务器,主要功能是提供网上信息浏览服务,Web服务器可以解析HTTP协议。当Web服务器接收到一个HTTP请求,会返回一个HTTP响应,例如送回一个HTML页面。为了处理一个请求Web服务器可以响应一个静态页面或图片,进行页面跳转或者把动态响应的产生委托给一些其它的程序例如CGI脚本,JSP脚本,servlets,ASP脚本,服务器端JavaScript,或者一些其它的服务器端技术。 -关于HTTP协议详细信息可以查看[网络协议之HTTP](https://www.wdbyte.com/2018/07/computer/protocol-http/) - -几种常见的Web服务器。 -1. Resin -Resin是CAUCHO公司的产品,是一个非常流行的application server,对servlet和JSP提供了良好的支持,性能也比较优良,resin自身采用JAVA语言开发。 -1. JBoss -是一个基于J2EE的开放源代码的应用服务器。 JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用。JBoss是一个管理EJB的容器和服务器,支持EJB 1.1、EJB 2.0和EJB3的规范。但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。 -1. WebSphere -WebSphere 是 IBM 的软件平台。它包含了编写、运行和监视全天候的工业强度的随需应变 Web 应用程序和跨平台、跨产品解决方案所需要的整个中间件基础设施,如服务器、服务和工具。WebSphere 提供了可靠、灵活和健壮的软件。 -1. WebLogic -WebLogic是美国Oracle公司出品的一个application server,确切的说是一个基于JAVAEE架构的中间件,WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器。将Java的动态功能和Java Enterprise标准的安全性引入大型网络应用的开发、集成、部署和管理之中。 -1. Tomcat -Tomcat是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目,由Apache、Sun 和其他一些公司及个人共同开发而成。由于有了Sun 的参与和支持,最新的Servlet 和JSP 规范总是能在Tomcat 中得到体现,Tomcat 5支持最新的Servlet 2.4 和JSP 2.0 规范。因为Tomcat 技术先进、性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应用服务器。 - - - -### 安装与配置JDK - -Java web开发首先我们需要配置web服务器,这样我们才能通过web服务器部署发布web项目,才可以进行访问,这里选择Tomcat作为web服务器,Tomcat基于运行基于Jre环境,因此我们在配置Tomcat 之前需要配置Java环境。 - -jdk的下载与安装这里不说,只顺便说一下win下环境变量的配置。 - -1. 配置环境变量 :右键我的电脑 → 属性→ 高级系统设置 → 环境变量 -1. 在系统变量里新建JAVA_HOME 变量,值为JDK安装路径 -1. 新建`classpath`变量,值为: - .;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar - -1. 修改PATH变量值,在值后添加: - %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin - -配置完毕后运行命令java -version有版本信息打印说明配置成功 -```dos -Microsoft Windows [版本 10.0.15063] -(c) 2017 Microsoft Corporation。保留所有权利。 - -C:\Users\Niu>java -version -java version "1.8.0_111" -Java(TM) SE Runtime Environment (build 1.8.0_111-b14) -Java HotSpot(TM) 64-Bit Server VM (build 25.111-b14, mixed mode) - -C:\Users\Niu> -``` -### 安装与启动Tomcat -Tomcat是免费的开源软件,可在直接在官方网站下载。 -[http://tomcat.apache.org/](http://tomcat.apache.org/) -1.可以直接在左侧选择版本: - -![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ae2daaf8a3849968ec40589cdcc54f4b.jpg) - -2.选择版本后可以在下方进行下载,可以选择下载安装版或者解压版,这里选择了解压版 -tar.gz文件是Linux操作系统下的安装版本 -exe文件是Windows系统下的安装版本 -zip文件是Windows系统下的压缩版本 - -![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5479a7ba9d098838d3e0fee1d2f05877.jpg) - -3.下载完成后解压缩,得到Tomcat目录, - -![Tomca目录](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/869cfe4d5dc8a32e1e1b7f7723587a53.jpg) - -4.可以在%Tomcat%\conf\server.xml中修改默认端口号(默认为8080) - -```xml - -``` -5.配置完毕后可以运行%Tomcat%\bin\startup.sh进行启动 - -![Tomcat启动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6eee55ca7cc387416c7f0bcc4fa245f9.jpg) - -6.浏览器访问http://localhost:8080 进行测试 - -![Tomcat访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/8d9b6b2f556a6305d8d20a194aba1869.jpg) - -#### Eclipse中使用Tomcat -点击菜单:Window → Prefences → Server → Runtime Environments -点击左边Add按钮,选择Apache,选择Tomcat版本,点击NEXT - -![Eclipse添加Tomcat](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/00aba424b3e589bc32e556f2c2409566.jpg) - - -至此,Java Web集成开发环境配置完成,可以在Eclipse中Server面板中新建Tomcat Server进行启动。 - -新建Server: - -![新建Server](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/0e5978714a480e71652775e9819e46db.jpg) - -完成创建: - -![完成server创建](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/c360b946bd1e0805f38fb3057683eb9e.jpg) - -完成创建之后可以在新建的server上右键选择start进行启动,启动效果如同双击startup.bat,console控制台会同步显示启动信息。 - -### web项目开发 - -在环境搭建完成之后,我们应该使用这个环境进行Web项目的开发。这里使用一个简单的例子,来演示如何使用Eclipse开发并且部署一个Web项目。 - -#### 创建web项目 -首先我们打开Eclipse,点击菜单File -> new -> project 新建项目 - -![新建项目here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/aec15cfa216538721861005372b29b45.jpg) - -选择Web Project项目进行创建 - -![创建项目](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1f3a7bd37a8abc15e8e85c1963d7f951.jpg) - -填写项目名称等信息 - -![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/597f7b796de57cfae5b774f93a66709b.jpg) - -点击两次Next之后,创建web.xm信息,完成项目创建 - -![创建web.xml](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4741b851105cb98eeeba23234936bd11.jpg) - -查看项目目录结构 -![web项目目录结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/78f64a263de3943d92aa14e019673308.jpg) - - -为了实验效果,我们在WebContent下创建一个index.html文件,编写Hello web内容进行部署演示。 -WebContent 上右键 NEW - >File - >输入文件名index.html完成文件创建。 -编写内容: - -```html - - - - Web test - - -

Hello web

- - -``` -#### 部署web项目 -在eclipse中使用Tomcat进行启动项目 -在已经创建完毕的Tomcat server上右键添加项目,然后进行启动即可 - -![在Eclipse使用Tomcat启动Web项目 here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/a679a296bfc9d79297f11199958cf153.gif) - -#### 访问web项目 -可以看到项目启动在8080端口,启动完毕,此时可以通过浏览器进行项目访问。 - -http://localhost:8080/web-Test/index.html - -![访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/57549829bfef115af813a93c64751e4c.jpg) - -此时,在Eclipse中创建部署启动一个Web项目已经完成。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/develop/web-base-02.md b/docs/develop/web-base-02.md deleted file mode 100644 index de3ed6b..0000000 --- a/docs/develop/web-base-02.md +++ /dev/null @@ -1,189 +0,0 @@ ---- -title: Web笔记(二)Tomcat 使用总结 -date: 2018-08-23 09:12:31 -url: develop/web/web/base-02 -updated: 2018-08-23 09:12:31 -categories: - - Java 开发 -tags: -- Tomcat -- Java EE ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/84e22fa4d78bc3eb8ed5109a0beb21f8.jpg) -### Tomcat 介绍 -Tomcat是由Apache软件基金会下属的Jakarta项目开发的一个Servlet容器,按照Sun Microsystems提供的技术规范,实现了对Servlet和JavaServer Page(JSP)的支持,并提供了作为Web服务器的一些特有功能,如Tomcat管理和控制平台、安全域管理和Tomcat阀等。由于Tomcat本身也内含了一个HTTP服务器,它也可以被视作一个单独的Web服务器。但是,不能将Tomcat和Apache HTTP服务器混淆,Apache HTTP服务器是一个用C语言实现的HTTPWeb服务器;这两个HTTP web server不是捆绑在一起的。Apache Tomcat包含了一个配置管理工具,也可以通过编辑XML格式的配置文件来进行配置。(摘录自[Wiki](https://zh.wikipedia.org/wiki/Apache_Tomcat))([Apache Tomcat](http://tomcat.apache.org/)) - - -### Tomcat 安装 -Tomcat是免费的开源软件,可在直接在官方网站下载。 -[http://tomcat.apache.org/](http://tomcat.apache.org/) -1.可以直接在左侧选择版本: - -![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ae2daaf8a3849968ec40589cdcc54f4b.jpg) - -2.选择版本后可以在下方进行下载,可以选择下载安装版或者解压版,这里选择了解压版 -tar.gz文件是Linux操作系统下的安装版本 -exe文件是Windows系统下的安装版本 -zip文件是Windows系统下的压缩版本 - -![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5479a7ba9d098838d3e0fee1d2f05877.jpg) - -3.下载完成后解压缩,得到Tomcat目录, - -![Tomca目录](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/869cfe4d5dc8a32e1e1b7f7723587a53.jpg) - -### Tomcat 的默认端口 - -Tomcat 修改HTTP默认端口,可以直接修改Tomcat 目录下\conf \server.xml文件。默认端口号为8080,修改为想要的端口,重启Tomcat即可。 -```xml - -``` -若要修改成8081端口: -```xml - -``` -### Tomcat 的虚拟目录配置 -什么是虚拟目录呢,简单的说,我们可以根据请求的路径不同,来发布不同的项目。如此形式:我们想要 -在访问http://www.codingme.net/testA 时,进入A项目。 -在访问http://www.codingme.net/testB 时,进入B项目。 -这个时候我们就需要配置虚拟目录来完成这个操作。 -此时的URL:http://www.codingme.net/testA 不是单纯的路径,而是协议域名端口号+WEB应用testA。 -#### 自动映射虚拟目录 -在Tomcat 默认情况下,我们可以看到 %Tomca%\conf \server.xml文件最底部有配置如下: - -```xml - - - - - - -``` -这里Tomcat 默认配置了虚拟主机localhost,基础应用目录webapps,也就是%Tomca%\webapps\ 目录,Tomcat 服务器会自动管理webapps目录下的所有web应用,并把它映射成虚似目录。 -因此在需要配置虚拟目录时我们可以直接把项目复制到webapps 目录下进行发布 -示例: -1. 复制项目到webapps下 - -![复制项目到webapps](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/73a4fd3da0339281189dd33b0b417eba.jpg) -2. 查看test/ index.html 文件内容 - ![Index内容](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/f9501721ef675ded41268e0080ef05bc.jpg) -3. 运行%Tomcat%/bin/startup.bat 启动Tomcat -4. 进行访问测试http://localhost:8080/test/index.html - -![访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/b6b0c70169fe8297bec33d9f51980a72.jpg) - -注意:http://localhost:8080 会默认访问 webapps/ROOT文件夹中的内容。 - -#### 修改server.xml 映射虚拟目录 - -我们也可以通过在server.xml 文件中的host 元素之间添加配置代码来配置虚拟目录:添加代码如下; - -```xml -Context path="/app" docBase="D:/app" debug="0" reloadable="true" crossContext="true"/> -``` - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6a0ecaf86e878e286eb489d0b5b673d2.jpg) - -配置中我们配置了访问路径为/app ,项目路径D:/app 因此我们只要把要发布的内容放入D:/app 文件夹中即可。 - 配置完毕后 运行%Tomcat%/bin/startup.bat 启动Tomcat,可以通过 -> http://localhost:8080/app/index.html - -访问到D:/app/index.html,需要多个虚拟目录可以直接配置多条配置。 - -注意:若想使用http://localhost:8080 访问项目则需要配置 `path=""` -注意:配置中的path值和docBase中的文件夹名称没有任何关系。 - -#### 在conf /Catalina /localhost 增加xml文件映射虚拟目录 - -博主比较推荐这一种。 -在%Tomcat%/conf/Catalina/localhost目录中,增加配置文件来配置虚拟目录。 -配置文件名称格式为:虚拟目录路径.xml -举个栗子: -在%Tomcat%/conf/Catalina/localhost目录中增加文件 `blog.xml` -写入内容: - -```xml - - - -``` -![Tomcat-localhost](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1a56a5580ae151d1b7201e11fd04bed8.jpg) - -D盘下blog文件夹中内容: - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ab3ac58378336d818a7386d18e887eab.jpg) - -`docBase="d:/blog" ` 指定了Web应用存放位置为D盘下blog文件夹中。 -`reloadable="true" `表示当blog文件夹中文件有变化中,自动加载。 -配置完毕启动Tomcat后就可以通过访问到blog文件夹中的内容。 - -> http://localhost:8080/blog - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5ca799c38a020dbd5ae476420537d3bc.jpg) - -注意:需要多个虚拟目录可以多建立几个配置文件,文件名不能相同。 -注意:若想使用http://localhost:8080 访问项目则需要配置文件名为`ROOT.xml`。 -注意:文件名blog.xml 和配置中d:/blog 没有关系,文件名可以写成其他,如blog111.xml,那么在访问时就要访问http://localhost:8080/blog111 - -### Tomcat 的虚拟主机 - -首先我们先了解虚拟主机的用处,一个虚拟主机也就是一个网站。比如我们`只有一个服务器`,一个服务器上只有一个80端口,我们需要发布两个web项目,那么我们可以使用虚拟目录,把两个项目发布到两个不同的路径之下,但是如果我们有两个不同的域名需要把两个项目对应两个域名,我们就需要配置虚拟主机了。 - -示例:两个域名 -一个是`www.aaaa.com` -一个是 `www.bbbb.com` - -为了实验效果,我们配置C:\Windows\System32\drivers\etc\hosts 文件,添加两行映射。 -``` -127.0.0.1 www.aaaa.com -127.0.0.1 www.bbbb.com -``` -此时我们本机可以使用 localhost / www.aaaa.com / www.bbbb.com 进行访问。 - -配置虚拟主机指定www.aaaa.com访问是的内容。 -1:在server.xml中Engine元素中添加一个host子元素 -```xml - - - -``` -其中的 -1. name表示在访问的域名是www.aaaa.com时会使用此配置。 -1. appBase="d:/aaaa" 指定了项目的发布路径。 - -启动Tomcat后,此时在使用www.aaaa.com进行访问时候,会默认显示d:/aaaa/ROOT中的内容。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/c437877b962ffd9aac1f3beb2b9c074c.gif) - -如果存在 d:/aaaa/test,则可以通过http://www.aaaa.com:8080/test 进行访问。 -此时test也就是www.aaaa.com的虚拟目录。 -添加www.bbbb.com 访问同上。 - -### Tomcat 的单例多实例配置 -参考之前文章 -[Linux配置Tomcat的单机多实例](https://www.wdbyte.com/2018/08/develop/install-tomcat-many-instance/) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/develop/web-base-03.md b/docs/develop/web-base-03.md deleted file mode 100644 index 1bcf2ab..0000000 --- a/docs/develop/web-base-03.md +++ /dev/null @@ -1,348 +0,0 @@ ---- -title: Web笔记(三)Servlet 的类与接口API -date: 2018-09-01 09:58:35 -url: develop/web/web/base-03 -updated: 2018-09-01 09:58:35 -categories: - - Java 开发 -tags: - - Servlet ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -### Servlet 介绍 -Java Servlet 是运行在 Web 服务器或应用服务器上的程序,它是作为来自 Web 浏览器或其他 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。 -使用 Servlet,您可以收集来自网页表单的用户输入,呈现来自数据库或者其他源的记录,还可以动态创建网页。 -Java Servlet 通常情况下与使用 CGI(Common Gateway Interface,公共网关接口)实现的程序可以达到异曲同工的效果。但是相比于 CGI,Servlet 有以下几点优势: - - 性能明显更好。 - - Servlet 在 Web 服务器的地址空间内执行。这样它就没有必要再创建一个单独的进程来处理每个客户端请求。 - - Servlet 是独立于平台的,因为它们是用 Java 编写的。 - - 服务器上的 Java 安全管理器执行了一系列限制,以保护服务器计算机上的资源。因此,Servlet 是可信的。 - - Java 类库的全部功能对 Servlet 来说都是可用的。它可以通过 sockets 和 RMI 机制与 applets、数据库或其他软件进行交互。 - - -### Servlet API -Servlet API 包含两个包,分别是:javax.servlet 与javax.servlet.http包。具体的类与接口结构如下图: - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/68c057bb4390b74f4ebb980719345e2a.jpg) - -Servlet 的运行需要Web 服务器的支持,Web服务器可以通过调用Servlet对象提提供的标准API ,对客户端请求做出处理。 -[官方API](https://tomcat.apache.org/tomcat-5.5-doc/servletapi/) - -#### Servlet 接口 - 定义所有 servlet 都必须实现的方法。 -servlet 是运行在 Web 服务器中的小型 Java 程序。servlet 通常通过 HTTP(超文本传输协议)接收和响应来自 Web 客户端的请求。 -要实现此接口,可以编写一个扩展 javax.servlet.GenericServlet 的一般 servlet,或者编写一个扩展 javax.servlet.http.HttpServlet 的 HTTP servlet。 -此接口定义了初始化 servlet 的方法、为请求提供服务的方法和从服务器移除 servlet 的方法。这些方法称为生命周期方法,它们是按以下顺序调用的: - -1. 构造 servlet,然后使用 init 方法将其初始化。 -2. 处理来自客户端的对 service 方法的所有调用。 -3. 从服务中取出 servlet,然后使用 destroy 方法销毁它,最后进行垃圾回收并终止它。 - -除了生命周期方法之外,此接口还提供了 getServletConfig 方法和 getServletInfo 方法,servlet 可使用前一种方法获得任何启动信息,而后一种方法允许 servlet 返回有关其自身的基本信息,比如作者、版本和版权。 - -Servlet 接口中定义的方法如下: - -```java -//当Servlet将要卸载时由Servlet引擎调用,用于是释放资源 -void destroy() - -//由 servlet 容器调用,指示将该 servlet 放入服务。servlet 容器 -//仅在实例化 servlet 之后调用 init 方法一次。在 servlet 可以接 -//收任何请求之前,init 方法必须成功完成。 -void init(ServletConfig config) throws ServletException - -//由 servlet 容器调用,以允许 servlet 响应某个请求。此方法仅在 -//servlet 的 init() 方法成功完成之后调用。 -void service(ServletRequest req, ServletResponse res) throws ServletException, java.io.IOException - -//返回 ServletConfig 对象,该对象包含此 servlet 的初始化和启动 -//参数。返回的 ServletConfig 对象是传递给 init 方法的对象。此接 -//口的实现负责存储 ServletConfig 对象,以便此方法可以返回该对象。 -//实现此接口的 GenericServlet 类已经这样做了。 -ServletConfig getServletConfig() - -// 返回有关 servlet 的信息,比如作者、版本和版权。。 -String getServletInfo() - -``` - -#### ServletConfig 接口 -由Servlet接口中的Init方法我们可以发现,init方法在做初始化工作的时候会传入ServletConfig类型的参数进行初始化工作, Servlet 容器使用的 ServletConfig配置对象,该对象在初始化期间将信息传递给 Servlet。 - -ServletConfig 接口中定义的方法如下: - -```java -//返回包含指定初始化参数的值的 String,如果参数不存在,则返回 null。 -String getInitParameter(String name) - -//返回一个存储所有初始化变量的枚举函数,如果Servlet没有初始化变量, -//返回一个空的枚举函数 -java.util.Enumeration getInitParameterNames() - -//返回一个ServletConfig 对象的引用 -ServletContext getServletContext() - -//返回此 servlet 实例的名称。该名称可能是通过服务器管理提供的 -//在 Web 应用程序部署描述符中分配,或者对于未注册(和未命名) -//的 servlet 实例,该名称将是该 servlet 的类名称。 -String getServletName() - -``` - -#### GenericServlet 接口 - 定义一般的、与协议无关的 Servlet。要编写用于 Web 上的 HTTP Servlet,请改为扩展 javax.servlet.http.HttpServlet。GenericServlet 实现 Servlet 和 ServletConfig 接口。**实现了Servlet 接口中除service()方法之外的所有抽象方法,但是是默认实现。**Servlet可以直接扩展 GenericServlet,GenericServlet 使编写 Servlet变得更容易。它提供生命周期方法 init 和 destroy 的简单版本,以及 ServletConfig 接口中的方法的简单版本。GenericServlet 还实现 log 方法,在 ServletContext 接口中对此进行了声明。 - -要编写一般的 servlet,只需重写抽象 service 方法即可。在开发中很少使用,不再说明。 -#### HttpServlet 接口 -HttpServlet 类继承GenericServlet 类,是一个抽象类。在Java Web 开发中自定义Servlet 通常都是直接集成HttpServlet 类,此类实现了Servlet 接口中的service(ServletRequest request,ServletResponse response) 方法,用来处理客户端请求,根据request中的getMethod方法来调用不同的处理方法,例如,get方法,则调用doGet方法进行处理,也因此HttpServlet定义了处理不同请求的不同方法。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7ae09229e2b45fc552e9b2ab3b30d31e.jpg) - - -#### ServletRequest 和 ServletResponse 接口 - 定义将客户端请求信息提供给某个 servlet 的对象。servlet 容器创建 ServletRequest 对象,并将该对象作为参数传递给该 servlet 的 service 方法。 -ServletRequest 对象提供包括参数名称、参数值、属性和输入流的数据。扩展 ServletRequest 的接口可提供其他特定于协议的数据,例如 javax.servlet.http.HttpServletRequest 提供的 HTTP 数据。 - -ServletRequest中有很多有用的方法: -```java -//返回指定servlet上下文(web应用)的URL的前缀。 -String getContextPath() - -//返回请求URI上下文后的子串 -String getServletPath() - -//返回指定的HTTP头标指。如果其由请求给出,则名字应为大小写不敏感。 -String getHeader(String name) - -//返回具有指定名字的请求属性,如果不存在则返回null。属性可由servlet -//引擎设置或使用setAttribute()显式加入。 -Object getAttribute(String name) - -//以指定名称保存请求中指定对象的引用。 -void setAttribute(String name,Object obj) - -//返回指定输入参数,如果不存在,返回null。 -String getParameter(String name) - -//返回请求所用的字符编码。 -String getCharacteEncoding() - -``` -具体可以参考官方API -[ServletRequest API](https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletRequest.html) - -ServletResponse 中有很多有用的方法: -```java -//返回响应使用字符解码的名字。除非显式设置,否则为ISO-8859-1 -String getCharacterEncoding() - -//返回用于将返回的二进制输出写入客户端的流,此方法和getWrite() -//方法二者只能调用其一。 -OutputStream getOutputStream()throws IOException - -//返回用于将返回的文本输出写入客户端的一个字符写入器,此方法和 -//getOutputStream()二者只能调用其一。 -Writer getWriter()throws IOException - -//设置内容类型。在HTTP servlet中即设置Content-Type头标。 -void setContentType(String type) -``` -具体可以参考官方API -[ServletResponse API](https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletResponse.html) - -#### HttpServletRequest 和 HttpServletResponse 接口 -HttpServletRequest 是专用于HTTP的ServletRequest 子接口,客户端浏览器的请求被封装成一个HttpServletRequest 对象,请求信息请求的地址,参数,数据,上传的文件,IP地址,甚至操作系统信息都包含在内。 -主要处理: - -1. 读取和写入HTTP头标 -2. 取得和设置cookies -3. 取得路径信息 -4. 标识HTTP会话。 - -HttpServletRequest 常用方法: -```java -//返回与请求相关cookie的一个数组。 -Cookie[] getCookies() - -//返回HTTP请求方法(例如GET、POST等等) -String getMethod() - - //返回客户端的会话ID -String getRequestedSessionId() - - //调用getSession(true)的简化版。 -HttpSession getSession() - -//返回当前HTTP会话,如果不存在,则创建一个新的会话,create参数为true。 -HttpSession getSession(boolean create) - -//返回URL中一部分,从“/”开始,包括上下文,但不包括任意查询字符串。 -String getRequestURI() - -//返回查询字符串,即URL中?后面的部份。 -String getQueryString() - -//返回指定servlet上下文(web应用)的URL的前缀。 -String getContextPath() -``` -HttpServletResponse 则继承了ServletResponse 接口。都提供了与HTTP有关的方法,主要是对HTTP状态码和Cookie的管理。 -```java -//将一个Set-Cookie头标加入到响应。 -void addCookie(Cookie cookie) - -//设置响应状态码为指定值(可选的状态信息) -void sendError(int status) - -//重定向到某一个Web组件 -void sendRedirect(String location) -``` - -#### ServletContext 接口 -一个ServletContext 接口对象表示一个Web应用程序的上下文,运行在Java虚拟机中每个Web应用程序都有一个与之香瓜你的Servlet上下文,Sevetl API提供了一个ServletContext 接口老表示上下文,它提供了一些方法可以让Servlet 通过这些方法与Servlet 容器进行通信。 -常用方法: -```java -//将对象绑定到此 servlet 上下文中的给定属性名称。若存在,则替换之 -void setAttribute(String name, Object object) - -//返回Servlet 上下文中名字为name的对象,名字是name的对象是全局对象 -//因此可以被同一个Servlet 在任意时候访问,或者上下文中任意其他的Servlet访问 -Object getAttribute(String name) - -// 返回包含指定上下文范围初始化参数值的 String,如果参数不存在,则返回 null -String getInitParameter(String name) -``` -[ServletContext 接口官方API](https://tomcat.apache.org/tomcat-5.5-doc/servletapi/javax/servlet/ServletContext.html) - -#### HttpSession 接口 - 提供一种方式,跨多个页面请求或对 Web 站点的多次访问标识用户并存储有关该用户的信息。 -HttpSession 接口用于记录HTTP客户端和HTTP会话之间的关联,且可以在多个连接和请求中持续一段时间,Session则可以让无状态的HTTP请求在多个请求时识别用户记录状态。 - -此接口允许 servlet -查看和操作有关某个会话的信息,比如会话标识符、创建时间和最后一次访问时间 -将对象绑定到会话,允许跨多个用户连接保留用户信息。 - -常用方法: -```java - -//使此会话无效,然后取消对任何绑定到它的对象的绑定。 -void invalidate() - -//从此会话中移除与指定名称绑定在一起的对象。如果会话没有与 -//指定名称绑定在一起的对象,则此方法不执行任何操作。 -void removeAttribute(String name) - -//使用指定名称将对象绑定到此会话。如果具有同样名称的对象已 -//经绑定到该会话,则替换该对象。 - void setAttribute(String name, Object value) - -//返回与此会话中的指定名称绑定在一起的对象,如果没有对象绑 -//定在该名称下,则返回 null。 - Object getAttribute(String name) - -//返回创建此会话的时间,该时间是用自格林威治标准时间 1970 -//年 1 月 1 日午夜起经过的毫秒数来测量的。 -long getCreationTime() - -//返回包含分配给此会话的唯一标识符的字符串。 -String getId() - -//超时时间以秒为单位。负数则永不超时。 -void setMaxInactiveInterval(int interval) - -//返回 servlet 容器在客户端访问之间将使此会话保持打开状态 -//的最大时间间隔,以秒为单位。 -int getMaxInactiveInterval() -``` - - -### 附录:HttpServletRequest的请求信息获取 - -```java -/** - * HttpServletRequest请求参数获取测试 - * @author niumoo - */ -@WebServlet("/dispense") -public class DispenseServlet extends HttpServlet { - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - // 获取请求方式:GET - System.out.println("req.getMethod():" + req.getMethod()); - - // 获取项目名称:/dispense - System.out.println("req.getContextPath():" + req.getContextPath()); - - // 获取完整请求路径:http://localhost:8888/dispense/dispense - System.out.println("req.getRequestURL():" + req.getRequestURL()); - - // 获取除了域名外的请求数据:/dispense/dispense - System.out.println("req.getRequestURI():" + req.getRequestURI()); - - // 获取请求参数:name=codingme.net - System.out.println("req.getQueryString():" + req.getQueryString()); - // 获取请求头: - System.out.println("req.getHeader(\"user-Agent\"):" + req.getHeader("user-Agent")); - - System.out.println("-------------------------------------------------"); - // 获取所有的消息头名称 - Enumeration headerNames = req.getHeaderNames(); - // 获取获取的消息头名称,获取对应的值,并输出 - while (headerNames.hasMoreElements()) { - String nextElement = headerNames.nextElement(); - System.out.println(nextElement + ":" + req.getHeader(nextElement)); - } - - System.out.println("---------------------------------------------------"); - // 根据名称获取此重名的所有数据 - System.out.println("req.getHeader(\"accept\"):" + req.getHeader("accept")); - - // 获取请求主机名 - System.out.println("req.getHeader(\"host\"):" + req.getHeader("host")); - - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) - throws ServletException, IOException { - doGet(req, resp); - } -} - -``` -请求:http://localhost:8888/dispense/dispense?name=codingme.net -输出: - -``` -req.getMethod():GET -req.getContextPath():/dispense -req.getRequestURL():http://localhost:8888/dispense/dispense -req.getRequestURI():/dispense/dispense -req.getQueryString():name=codingme.net -req.getHeader("user-Agent"):Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3080.5 Safari/537.36 --------------------------------------------------------- -host:localhost:8888 -connection:keep-alive -cache-control:max-age=0 -upgrade-insecure-requests:1 -user-agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3080.5 Safari/537.36 -accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 -accept-encoding:gzip, deflate, br -accept-language:zh-CN,zh;q=0.8 -cookie:_ga=GA1.1.1003706294.1499565784; Hm_lvt_57ccbd5c600ed4e6bdb9458e666b6409=1499849256,1499853602,1499950574; Hm_lvt_1b51c3ea9a3e7b1a2bc55df97ab4efd3=1499952403 ----------------------------------------------------------- -req.getHeader("accept"):text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 -req.getHeader("host"):localhost:8888 -``` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/develop/web-base-04.md b/docs/develop/web-base-04.md deleted file mode 100644 index c928d93..0000000 --- a/docs/develop/web-base-04.md +++ /dev/null @@ -1,295 +0,0 @@ ---- -title: Web笔记(四)Servlet 程序开发 -date: 2018-09-04 20:35:44 -url: develop/web/web/base-04 -updated: 2018-09-04 20:35:44 -categories: - - Java 开发 -tags: - - Servlet ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -### Servlet 的编写 -Servlet本质上是一个Java类,创建一个Servlet很简单,就是定义一个Java类,这个类继承自`javax.servlet.http.HttpServlet`类,覆盖其中的doGet和doPost方法,在doGet和doPost中编写处理请求的代码。 -由于 Servlet 不是 Java 平台标准版的组成部分,所以必须添加jar包:`servlet-api.jar` -可以在Tomcat下lib目录下找到,也可自行下载添加。在创建Web项目的时候如果有选择Tomcat ,则会自动添加,如何创建一个web项目并部署到Tomcat,可以参考之前文章。 -[Web笔记(一) Web 简介与开发环境搭建 -](https://www.wdbyte.com/2018/08/develop/web/web-base-01/) - - -编写第一个Servlet ,我们可以直接在Eclipse 中的Web项目里新建Servelt。 -![创建servlet](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/541e00d4849885d86f4f7795d3c4e1a8.png) - -也可以普通创建类手动继承HttpServlet。在创建完毕后需要手动重写doGet和doPost方法。 -![创建servlet](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ac0664ef704f0e97e5e657bf849605da.png) - -使用第一种方法新建 FirstServlet.java 类(注意看代码注释) -```java -package net.codingme.servlet; - -import java.io.IOException; -import java.io.PrintWriter; -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -/** - * 第一个servlet测试 - * - */ -//@WebServlet("/firstServlet")  -public class FirstServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - public FirstServlet() { - super(); - } - - // get请求处理 - protected void doGet(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - //设置响应的文本类型和字符编码 - response.setContentType("text/html;charset=UTF-8"); - //通过输出流向客户端做出响应 - PrintWriter out = response.getWriter(); - out.println(""); - out.println("

firest servlet

"); - out.println(""); - out.close(); - } - - protected void doPost(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - doGet(request, response); - } - -} - -``` -HTTP的请求方式除了`get`和`post`请求之外还有put、delete等,get和post是最为常用两种请求方式,所以通常只覆盖doGet和doPost方法。 -`response.setContentType("text/html;charset=UTF-8");`用于设置响应的文本类型和编码方式,通过响应对象获得输出流对象out,用于向客户端浏览器输出响应内容,代码中输出的HTML标记,这实际上是一个动态的Web的页面。 -关于HTTP协议的相关知识可以查看文章[网络协议之HTTP](https://www.wdbyte.com/2018/07/computer/protocol-http/) - - -### Servlet 的web.xml配置 -Servlet编写完成之后,需要在工程`WEBROOT/WEB-INF/web.xml`中进行配置才可以生效,web.xml是Web应用的主配置文件,包含Web应用配置的主要信息。 - -在web.xml中根元素``中配置Servlet,代码如下: -```java - - first - net.codingme.servlet.FirstServlet - - - - first - /firstServlet - -``` -配置解释: -```java - - servlet名 - servlet的class的全名 - - - - servlet名 - servlet的访问路径 - -``` - -### Servlet 3.0的注解配置 - -如果Servlet版本是`3.0`及以上的,可以使用`@WebServlet`注解进行配置,省去了web.xml中的繁琐配置。此方式和通过web.xml配置Servlet`二选一`即可。 - -**@WebServlet主要属性列表** - -| 属性名|类型|描述| -| ------------ | ------------ | ------------ | -| name |String | 指定 Servlet 的 name 属性,等价于 。如果没有显式指定,则该 Servlet 的取值即为类的全限定名。 | -| value | String[] | 该属性等价于 urlPatterns 属性。两个属性不能同时使用。 | -| urlPatterns | String[] | 指定一组 Servlet 的 URL 匹配模式。等价于 标签。 | -| loadOnStartup | int | 指定 Servlet 的加载顺序,等价于 标签。 | -| initParams |WebInitParam[] | 指定一组 Servlet 初始化参数,等价于 标签。 | -|asyncSupported |boolean | 声明 Servlet 是否支持异步操作模式,等价于 标签。 | -|description |String | 该 Servlet 的描述信息,等价于 标签。 | -| displayName |String | 该 Servlet 的显示名,通常配合工具使用,等价于 标签。 | - -**@WebServlet完整的使用例子** -```java -@WebServlet(urlPatterns = {"/simple"}, asyncSupported = true, -loadOnStartup = -1, name = "SimpleServlet", displayName = "ss", -initParams = {@WebInitParam(name = "username", value = "tom")} -) - -``` - -只配置访问路径的示例: -```java -package net.codingme.servlet; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - - -/** - * 第一个servlet测试 - * 使用注解配置访问路径 - */ - @WebServlet(urlPatterns= {"/firstServlet"}) -public class FirstServlet extends HttpServlet { - private static final long serialVersionUID = 1L; - - public FirstServlet() { - super(); - } - . - . - . -``` - -### Servlet 的运行与访问 -Servlet配置完成之后,把Servlet所在工程项目部署到web服务器上并启动。 -发布成功之后可以访问servlet配置的路径进行访问。 -格式如:`协议://服务器地址:端口号/WEB应用名/Servlet的访问路径` -那么web服务器是如何找到对应的servlet类的呢? -1. 查找web.xml中Servlet配置信息中的``值与请求路径相匹配的项。 -1. 访问到对应的(``中``与``中``值相等,)``,可以访问到指定的Servlet类。 -1. web服务器将在第一次访问servlet时实例化一个servlet对象用于处理请求。 - -访问Servlet有三种方式 - - 直接在浏览器地址中输入访问路径来访问 - - 通过超链接来访问 - - 通过提交表单来访问 - -访问测试: -![访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1c8602c60269fa6d2531c00d96d2398c.jpg) - - -### Servlet 的初始化参数 - -自定义的Servlet继承了了HttpServlet,HttpServlet继承了GenericServlet,GenericServlet类实现了ServletConfig接口,所以自定义的Servlet中可以直接使用ServletConfig中的`getInitParameter(String name)`方法。也可直接通过getServletName()方法获得Servlet名字,也就是web.xml中相应的``元素的``的值。如果没有配置会返回Servlet类名。 - - -#### 编写Servlet -```java -package net.codingme.servlet; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * - * Servlet获得初始化参数 - * - * @author Niu on 2017年7月25日 下午6:14:23 - */ -public class GetInitParamServlet extends HttpServlet { - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - //通过getInitParameter方法获得配置文件中设置的初始化值 - String name = this.getInitParameter("name"); - String password = this.getInitParameter("password"); - - //通过getServletName方法获得配置文件中设置Servlet名字 - String servletName = this.getServletName(); - - //设置响应文本类型和编码方式 - resp.setContentType("text/html;charset=UTF-8"); - - //通过输出流向客户端做出相应 - PrintWriter out = resp.getWriter(); - out.println("name:"+name); - out.println("
"); - out.println("password:"+password); - out.println("
"); - out.println("servletName:"+servletName); - out.close(); - } - - @Override - protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - doGet(req, resp); - } - -} -``` - - -#### 配置Servlet -初始化了两个参数,分别是`name:xy`和`password:123`,访问路径/getInitParam - -```xml - - - getInitParam - net.codingme.servlet.GetInitParamServlet - - - name - xy - - - password - 123 - - - - getInitParam - /getInitParam - - -``` - - -#### 测试Servlet -访问配置的路径:http://localhost:8080/servlet-GetInitParam/getInitParam -![访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/bfad3c21f6258b6bbdfd1d960ca72e74.png) -获取到配置的初始化参数。 - -**GitHub**:[获取初始化参数](https://github.com/niumoo/webcore/tree/master/servlet-GetInitParam) - -### Servlet 的开发总结 -Servlet开发步骤: -1. 编写Servlet,编写一个Java类,继承HttpServlet类并覆盖doGet和doPost方法 -2. 在配置文件web.xml中配置Servlet(Servlet3.0以上版本可以使用注解) -3. 将Servlet所在Java Web项目部署到Web服务器上,例如Tomcat -4. 启动Web服务器 -5. 请求访问Servlet - -Servlet执行流程: -1. 客户端浏览器向Web服务器发送请求访问某一个Servlet -1. Web服务器根据配置信息定位到具体的Servlet -3. 如果这个Servlet是第一次被访问,此时Servlet对象在内存中不存在,则创建这个Servlet对象,如果这个Servlet已经被访问过,则Servlet的对象已经存在内存中,然后创建一个线程操作这个Servlet对象,完成具体功能。 -4. 获得运行结果,通过响应对象(response)设置响应参数并将结果返回到客户端。 -5. 客户端将相应结果显示在浏览器中。 - - -GitHub:[第一个Servlet](https://github.com/niumoo/webcore/tree/master/servlet-First) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/develop/web-base-05.md b/docs/develop/web-base-05.md deleted file mode 100644 index 63d4a77..0000000 --- a/docs/develop/web-base-05.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: Web笔记(五)Servlet 的生命周期 -date: 2018-09-06 20:35:44 -url: develop/web/web/base-05 -updated: 2018-09-06 20:35:44 -categories: - - Java 开发 -tags: - - Servlet - - Java EE ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -Servlet的声明周期是由servlet的容器(web服务器)来控制的,通过简单的概括可以分为4步: - -Servlet类加载 → 实例化Servlet → Servlet提供服务 → 销毁Servlet。 - -1. Serlvet 类加载,该阶段仅仅执行一次。 -1. 实例化Servlet ,该阶段仅执行一次、 -1. Servlet 调用 service() 方法来处理客户端的请求,客户端请求一次执行一次,具体的执行次数取决于客户端的请求次数。 -1. Servlet 通过调用 destroy() 方法销毁Servlet,只在web服务器停止服务时执行一次。 - - -### 开发 -通过一个简单的例子来演示Servlet 的生命周期,编写 LiftServlet.java。 -```java -package net.codingme.servlet; - -import javax.servlet.annotation.WebServlet; -import java.io.IOException; - -import javax.servlet.ServletConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - * - * Desc:Servlet生命周期测试 - * - * @author niumoo on 2017年7月24日 上午11:04:40 - */ -//@WebServlet("/LiftServlet") -public class LiftServlet extends HttpServlet { - - // 不能被覆盖,这里是web服务器的servlet初始化 - @Override - public void init(ServletConfig config) throws ServletException { - System.out.println("Web服务器初始化Servlet : init(ServletConfi confit)"); - super.init(config); - } - - // 可以添加特定的初始化代码 - @Override - public void init() throws ServletException { - System.out.println("初始化init()"); - super.init(); - } - - // 不能被覆盖 - @Override - public void service(ServletRequest arg0, ServletResponse arg1) throws ServletException, IOException { - System.out.println("调用服务:service(ServletRequest arg0, ServletResponse arg1)"); - super.service(arg0, arg1); - } - - // service会检查请求类型,用来判断调用什么方法,不要覆盖 - @Override - protected void service(HttpServletRequest arg0, HttpServletResponse arg1) throws ServletException, IOException { - System.out.println("调用服务:service(HttpServletRequest arg0, HttpServletResponse arg1)"); - super.service(arg0, arg1); - } - - /** - * get方法 - */ - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - System.out.println("GET方法:doGet()"); - resp.getWriter().println("servlet get"); - } - - // 当web服务器正常停止时调用次销毁方法 - @Override - public void destroy() { - System.out.println("销毁:destroy()"); - super.destroy(); - } -} -``` - - -### 配置 -配置web.xml -```xml - - - liftServlet - net.codingme.servlet.LiftServlet - - - liftServlet - /liftServlet - -``` - -代码中配置了LiftServlet 的访问路径为/liftServlet。 - -### 访问 - -访问路径:http://localhost:8080/servlet-LifeCycle/liftServlet - -访问两次链接 ,观察控制台输出: - -``` -信息: Starting ProtocolHandler ["http-nio-8080"] -八月 06, 2017 11:32:09 上午 org.apache.coyote.AbstractProtocol start -信息: Starting ProtocolHandler ["ajp-nio-8009"] -八月 06, 2017 11:32:09 上午 org.apache.catalina.startup.Catalina start -信息: Server startup in 2705 ms -Web服务器初始化Servlet : init(ServletConfi confit) -初始化init() -调用服务:service(ServletRequest arg0, ServletResponse arg1) -调用服务:service(HttpServletRequest arg0, HttpServletResponse arg1) -GET方法:doGet() -调用服务:service(ServletRequest arg0, ServletResponse arg1) -调用服务:service(HttpServletRequest arg0, HttpServletResponse arg1) -GET方法:doGet() -八月 06, 2017 11:32:42 上午 org.apache.catalina.core.StandardServer await -信息: A valid shutdown command was received via the shutdown port. Stopping the Server instance. -八月 06, 2017 11:32:42 上午 org.apache.coyote.AbstractProtocol pause -信息: Pausing ProtocolHandler ["http-nio-8080"] -八月 06, 2017 11:32:42 上午 org.apache.coyote.AbstractProtocol pause -信息: Pausing ProtocolHandler ["ajp-nio-8009"] -八月 06, 2017 11:32:43 上午 org.apache.catalina.core.StandardService stopInternal -信息: Stopping service [Catalina] -八月 06, 2017 11:32:43 上午 org.apache.catalina.core.ApplicationContext log -信息: SessionListener: contextDestroyed() -八月 06, 2017 11:32:43 上午 org.apache.catalina.core.ApplicationContext log -信息: ContextListener: contextDestroyed() -销毁:destroy() -八月 06, 2017 11:32:43 上午 org.apache.coyote.AbstractProtocol stop -信息: Stopping ProtocolHandler ["http-nio-8080"] -八月 06, 2017 11:32:43 上午 org.apache.coyote.AbstractProtocol stop -信息: Stopping ProtocolHandler ["ajp-nio-8009"] -八月 06, 2017 11:32:43 上午 org.apache.coyote.AbstractProtocol destroy -信息: Destroying ProtocolHandler ["http-nio-8080"] -八月 06, 2017 11:32:43 上午 org.apache.coyote.AbstractProtocol destroy -信息: Destroying ProtocolHandler ["ajp-nio-8009"] -``` - -### 总结 - -1. **servlet初始化**:运行一次,调用`init(ServletConfig config)`创建ServletConfig对象。调用`init()` 创建servlet对象并将两者关联。可以将代码放入init()方法完成特定的初始化工作。 - -1. **servlet服务**:每次请求运行, 接收到请求通过`service(ServletRequest arg0, ServletResponse arg1)`方法进行处理,然后把请求对象封装成HttpServletRequest,响应对象封装成HttpServletResponse调用`service(HttpServletRequest arg0, HttpServletResponse arg1)`处理。在这个方法里会根据请求方式调用doGet或者doPost方法进行处理。 - -1. **servlet销毁**:运行一次,销毁Servlet 对象,销毁与之关联的ServletConfig对象, - -可以看到,在第一次访问时调用初始化代码,再次访问只调用服务代码,正常停止Tomcat,调用销毁代码。 - -**GitHub**:[Servlet 生命周期](https://github.com/niumoo/webcore/tree/master/servlet-LifeCycle) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/elasticsearch/elasticsearch-base-curd.md b/docs/elasticsearch/elasticsearch-base-curd.md deleted file mode 100644 index c12d95f..0000000 --- a/docs/elasticsearch/elasticsearch-base-curd.md +++ /dev/null @@ -1,814 +0,0 @@ ---- -title: 全文搜索ElasticSearch(一)数据的增删改查 -date: 2018-04-16 00:44:28 -url: lucene/elasticsearch-base-curd -tags: -- Elasticsearch -categories: -- Elasticsearch ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Elasticsearch logo](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/3d90604a4a775e8ce0450bac9bc4234c.jpg) - -ElasticSearch采用Java编写,提供简单易用的RESTFul API。我们可以直接通过API接口进行操作。 -笔者使用的ElasticSearch版本:elasticsearch-6.3.0 - - - -API基本格式 http://:/<索引>/<类型>/<文档id> -常用的HTTP动词 GET/PUT/POST/DELETE -[RESTFul资料](http://www.ruanyifeng.com/blog/2014/05/restful_api) - -## 索引创建 -### 非结构化创建 -使用Head插件直接点击添加索引创建。 -直接请求创建 $ curl -XPUT http://localhost:9200/book -​ -### 结构化创建 - -方式1:首先创建book索引 -``` -PUT http://localhost:9200/book。 -``` - -再添加novel类型以及定义结构。 -```json -POST http://localhost:9200/book/novel/_mappings -{ - "novel": { - "properties": { - "title": { - "type": "text" - } - } - } -} -``` - -方式2:直接创建people结构化索引指定配置信息已经数据结构。 -```json -// 创建 -PUT http://localhost:9200/people -{ - "settings":{ - "number_of_shards": 3, - "number_of_replicas": 1 - }, - "mappings":{ - "man":{ - "properties":{ - "name":{ - "type":"text" - }, - "country":{ - "type":"keyword" - }, - "age":{ - "type":"integer" - }, - "date":{ - "type":"date", - "format":"yyyy-MM-dd HH:mm:ss || yyyy-MM-dd || epoch_millis" - } - } - }, - "woman":{ - - } - } -} - -// 响应 -{ - "acknowledged": true, - "shards_acknowledged": true -} - -``` - -### 查看索引信息 - -查询一个索引是否为结构化索引,我们只需要查询索引信息,然后查看mappings属性是否有值,如果为空,则为非结构化索引,如果有相关的属性值,则是结构化索引。下面查看book索引信息: -```json -GET http://localhost:9200/book?pretty=true -{ - "book": { - "aliases": {}, - "mappings": { - "novel": { - "properties": { - "title": { - "type": "text" - } - } - } - }, - "settings": { - "index": { - "creation_date": "1523851142335", - "number_of_shards": "5", - "number_of_replicas": "1", - "uuid": "nbQPVXqqR--eRy_1S3OZqw", - "version": { - "created": "5050199" - }, - "provided_name": "book" - } - } - } -} -``` - -## 数据插入 - -### 根据文档ID插入 -直接POST请求 http://:/<索引>/<类型>/<文档id> -传入JSON数据就可以完成数据插入: -```json -POST http://localhost:9200/people/man/1 -{ - "name":"达西", - "conuntry":"China", - "age":24, - "date":"1994-11-01" -} - -// 响应 -{ - "_index": "people", - "_type": "man", - "_id": "1", - "_version": 1, - "result": "created", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - }, - "created": true -} -``` - -### 自动生成文档ID插入 -在发起POST请求的时候,不指定ID的值,ES会自动生成一个ID值。 -```json -POST http://localhost:9200/people/man -{ - "name":"超级玛丽", - "conuntry":"China", - "age":24, - "date":"1994-11-01" -} - -// 响应 -{ - "_index": "people", - "_type": "man", - "_id": "AWLNIi3-QcKhhHA2xN6t", // 自动生成的ID值 - "_version": 1, - "result": "created", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - }, - "created": true -} -``` - -## 数据修改 - -### 直接修改 -修改people索引中类型为man的ID为1的文档的name字段值为“曹操” -```json -POST http://localhost:9200/people/man/1/_update -{ - "doc":{ - "name":"曹操" - } -} - -// 响应 -{ - "_index": "people", - "_type": "man", - "_id": "1", - "_version": 5, - "result": "updated", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - } -} -``` - -### 脚本修改 -修改people索引中类型为man的ID为1的文档的age字段值为自增10。 -```json -POST http://localhost:9200/people/man/1/_update -{ - "script":{ - "lang":"painless", - "inline":"ctx._source.age+=10" - } -} - -// 响应 -{ - "_index": "people", - "_type": "man", - "_id": "1", - "_version": 9, - "result": "updated", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - } -} -``` -另外一种方式 -```json -POST http://localhost:9200/people/man/1/_update -{ - "script":{ - "lang":"painless", - "inline":"ctx._source.age=params.age", - "params":{ - "age":900 - } - } -} -// 响应 -{ - "_index": "people", - "_type": "man", - "_id": "1", - "_version": 10, - "result": "updated", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - } -} -``` - -## 删除 - -### 文档删除 -删除people索引中类型为man,ID为1的文档。 -```json -DELETE http://localhost:9200/people/man/1 - -// 响应 -{ - "found": true, - "_index": "people", - "_type": "man", - "_id": "1", - "_version": 11, - "result": "deleted", - "_shards": { - "total": 2, - "successful": 1, - "failed": 0 - } -} -``` - -### 索引删除 -删除people索引 -```json -DELETE http://localhost:9200/people - -// 响应 -{ - "acknowledged": true -} -``` - -## 基本查询 -### 数据准备 -创建book索引,设置结构: - -```json -POST http://localhost:9200/book/novel/_mappings -{ - "novel":{ - "properties":{ - "title":{ - "type":"text" - }, - "author":{ - "type":"text" - }, - "word_count":{ - "type":"integer" - }, - "publish_date":{ - "type":"date", - "format":"yyyy-MM-dd HH:mm:ss || yyyy-MM-dd || epoch_millis" - } - } - } -} - -// 响应 -{ - "acknowledged": true -} - -``` -录入用于测试的数据: -```json -{"title":"超级玛丽秘籍","author":"王五","word_count":5000,"publish_date":"1994-11-01"} -{"title":"三国志","author":"陈寿","word_count":30000,"publish_date":"1995-11-01"} -{"title":"三国演绎","author":"罗贯中","word_count":30000,"publish_date":"1994-03-01"} -{"title":"玛丽与管道工","author":"玛丽","word_count":10000,"publish_date":"1994-03-01"} -{"title":"史记","author":"司马迁","word_count":20000,"publish_date":"1994-08-01"} -{"title":"如何蹦的更高","author":"玛丽","word_count":20000,"publish_date":"1994-08-01"} -``` -## 查询 -### 全部查询 -查询book索引中类型为novel的所有文档信息。 -```json -GET localhost:9200/book/novel/_search?pretty=true - -// 响应 -{ - "took" : 80, // 花费时间 - "timed_out" : false, - "_shards" : { - "total" : 5, - "successful" : 5, - "failed" : 0 - }, - "hits" : { - "total" : 5, //总条数 - "max_score" : 1.0, - "hits" : [ - { - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOAAZzQcKhhHA2xN6z", - "_score" : 1.0, //评分 - "_source" : { - "title" : "超级玛丽秘籍", - "author" : "王五", - "word_count" : 5000, - "publish_date" : "1994-11-01" - } - }, - { - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOAEe5QcKhhHA2xN62", - "_score" : 1.0, - "_source" : { - "title" : "玛丽与管道工", - "author" : "陈寿", - "word_count" : 10000, - "publish_date" : "1994-03-01" - } - }, - { - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOAB8bQcKhhHA2xN60", - "_score" : 1.0, - "_source" : { - "title" : "三国志", - "author" : "陈寿", - "word_count" : 30000, - "publish_date" : "1995-11-01" - } - }, - { - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOADU4QcKhhHA2xN61", - "_score" : 1.0, - "_source" : { - "title" : "三国演绎", - "author" : "罗贯中", - "word_count" : 30000, - "publish_date" : "1994-03-01" - } - }, - { - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOAFiZQcKhhHA2xN63", - "_score" : 1.0, - "_source" : { - "title" : "史记", - "author" : "司马迁", - "word_count" : 20000, - "publish_date" : "1994-08-01" - } - } - ] - } -} - -``` - -### 精确查询 -查询book索引中类型为novel的ID为AWLOAFiZQcKhhHA2xN63的文档信息。 -```json -GET localhost:9200/book/novel/AWLOAFiZQcKhhHA2xN63?pretty=true -// 响应 -{ - "_index" : "book", - "_type" : "novel", - "_id" : "AWLOAFiZQcKhhHA2xN63", - "_version" : 1, - "found" : true, - "_source" : { - "title" : "史记", - "author" : "司马迁", - "word_count" : 20000, - "publish_date" : "1994-08-01" - } -} - -``` - - -### 限制查询条数 -搜索从第一条开始的1条数据。 -```json -POST http://localhost:9200/book/novel/_search -{ - "query":{ - "match_all":{} - }, - "from":1, - "size":1 -} - -// 响应 -{ - "took": 37, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 5, - "max_score": 1, - "hits": [ - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAEe5QcKhhHA2xN62", - "_score": 1, - "_source": { - "title": "玛丽与管道工", - "author": "陈寿", - "word_count": 10000, - "publish_date": "1994-03-01" - } - } - ] - } -} -``` - -### 关键词排序查询 -查询标题里含有“三国”的书籍,并且按出版日期降序排列。 - -```json -POST http://localhost:9200/book/novel/_search -{ - "query":{ - "match":{ - "title":"三国" - } - }, - "sort":[ - { - "publish_date":{"order":"desc"} - } - ] -} - -// 响应 -{ - "took": 200, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 2, - "max_score": 0.34450945, - "hits": [ - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAB8bQcKhhHA2xN60", - "_score": 0.34450945, - "_source": { - "title": "三国志", - "author": "陈寿", - "word_count": 30000, - "publish_date": "1995-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOADU4QcKhhHA2xN61", - "_score": 0.34450945, - "_source": { - "title": "三国演绎", - "author": "罗贯中", - "word_count": 30000, - "publish_date": "1994-03-01" - } - } - ] - } -} -``` - -### 聚合查询 -类似于数据库中的分组查询,下面的例子演示按照书籍字数统计数据,按照发布时间统计数量。 -```json -POST http://localhost:9200/book/novel/_search -{ - "aggs":{ - "group_by_word_count":{ - "terms":{ - "field":"word_count" - } - }, - "group_by_author":{ - "terms":{ - "field":"publish_date" - } - } - } - -} -// 响应 -{ - "took": 4, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 5, - "max_score": 1, - "hits": [ - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAAZzQcKhhHA2xN6z", - "_score": 1, - "_source": { - "title": "超级玛丽秘籍", - "author": "王五", - "word_count": 5000, - "publish_date": "1994-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAEe5QcKhhHA2xN62", - "_score": 1, - "_source": { - "title": "玛丽与管道工", - "author": "陈寿", - "word_count": 10000, - "publish_date": "1994-03-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAB8bQcKhhHA2xN60", - "_score": 1, - "_source": { - "title": "三国志", - "author": "陈寿", - "word_count": 30000, - "publish_date": "1995-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOADU4QcKhhHA2xN61", - "_score": 1, - "_source": { - "title": "三国演绎", - "author": "罗贯中", - "word_count": 30000, - "publish_date": "1994-03-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAFiZQcKhhHA2xN63", - "_score": 1, - "_source": { - "title": "史记", - "author": "司马迁", - "word_count": 20000, - "publish_date": "1994-08-01" - } - } - ] - }, - "aggregations": { - "group_by_word_count": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": 30000, - "doc_count": 2 - }, - { - "key": 5000, - "doc_count": 1 - }, - { - "key": 10000, - "doc_count": 1 - }, - { - "key": 20000, - "doc_count": 1 - } - ] - }, - "group_by_author": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": 762480000000, - "key_as_string": "1994-03-01 00:00:00", - "doc_count": 2 - }, - { - "key": 775699200000, - "key_as_string": "1994-08-01 00:00:00", - "doc_count": 1 - }, - { - "key": 783648000000, - "key_as_string": "1994-11-01 00:00:00", - "doc_count": 1 - }, - { - "key": 815184000000, - "key_as_string": "1995-11-01 00:00:00", - "doc_count": 1 - } - ] - } - } -} - -``` - -### 统计查询 -聚合查询也可以用于查询统计信息,下面的例子演示统计书籍字数信息,统计书籍字数最小的字数信息。 -```json -POST http://localhost:9200/book/novel/_search -{ - "aggs":{ - "grades_word_count":{ - "stats":{ - "field":"word_count" - } - }, - "min_word_count":{ - "min":{ - "field":"word_count" - } - } - } - -} -// 响应 -{ - "took": 44, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 5, - "max_score": 1, - "hits": [ - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAAZzQcKhhHA2xN6z", - "_score": 1, - "_source": { - "title": "超级玛丽秘籍", - "author": "王五", - "word_count": 5000, - "publish_date": "1994-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAEe5QcKhhHA2xN62", - "_score": 1, - "_source": { - "title": "玛丽与管道工", - "author": "陈寿", - "word_count": 10000, - "publish_date": "1994-03-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAB8bQcKhhHA2xN60", - "_score": 1, - "_source": { - "title": "三国志", - "author": "陈寿", - "word_count": 30000, - "publish_date": "1995-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOADU4QcKhhHA2xN61", - "_score": 1, - "_source": { - "title": "三国演绎", - "author": "罗贯中", - "word_count": 30000, - "publish_date": "1994-03-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAFiZQcKhhHA2xN63", - "_score": 1, - "_source": { - "title": "史记", - "author": "司马迁", - "word_count": 20000, - "publish_date": "1994-08-01" - } - } - ] - }, - "aggregations": { - "grades_word_count": { - "count": 5, - "min": 5000, - "max": 30000, - "avg": 19000, - "sum": 95000 - }, - "min_word_count": { - "value": 5000 - } - } -} -``` - - -## 参考资料 -[Elasticsearch Reference](https://www.elastic.co/guide/en/elasticsearch/reference/5.5/index.html) -[Elasticsearch: 权威指南](https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html) (低版本) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/elasticsearch/elasticsearch-plug-head.md b/docs/elasticsearch/elasticsearch-plug-head.md deleted file mode 100644 index 7fd948f..0000000 --- a/docs/elasticsearch/elasticsearch-plug-head.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: 全文搜索ElasticSearch(三)Head插件的安装与使用 -date: 2018-10-12 19:14:17 -url: lucene/elasticsearch-head -tags: -- Elasticsearch -- 插件 -- Elasticsearch Head -categories: -- Elasticsearch ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -![Chrome head安装](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/36fc267332d7fa3aeb1a49fb651c48bc.png) -### elasticsearch-head 介绍 - -`elasticsearch-head`是一个用于浏览器和与`elasticsearch`进行交互的Web前端程序。 `elasticsearch-head`是托管在github上的,可以自由的下载安装使用。 - -GitHub地址:https://github.com/mobz/elasticsearch-head - -### elasticsearch-head 下载 - -head插件可以直接在github页面上点击`clone or download`进行下载然后解压,也可以使用git命令进行下载。 - -`git clone git://github.com/mobz/elasticsearch-head.git` - -### elasticsearch-head 启动 - -#### 方式1:使用nodejs启动 - -这种方式需要使用[nodejs](https://nodejs.org/en/download/)环境进行启动。 - -1. `git clone git://github.com/mobz/elasticsearch-head.git` -1. `cd elasticsearch-head` -1. `npm install` -1. `npm run start` -1. `open` - -#### 方式2:使用Tomcat启动 - -观察解压的`elasticsearch-head` 目录和文件,我们发现head插件只是一个前端页面,因此我们可以运行于任何web服务器,如`Nginx`,`Tomcat`等。因为笔者开发环境是`JDK`,所以使用`Tomcat`进行测试。 -1. 解压Tomcat,进入webapps目录。 -2. 拷贝解压后的head插件内容到`webapps`文件夹。 -![image.png](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7405907d8c23c444dc105dc6c73997a5.png) -3. Tomcat启动。 -4. `open` http://localhost:8080/elasticsearch-head/ - -这时候如果启动了`elasticsearch`,会发现head插件并不能连接到`elasticsearch`,打开浏览器控制台会发现由于跨域问题产生的错误日志。 -![head跨域错误](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5a54b9ec54e63d18f4fa3454a04ad548.png) - -这时候我们需要配置`elasticsearch`允许跨域访问,打开`elasticsearch`的配置文件config/elasticsearch.yml在里面添加允许跨域配置。 -``` -# 跨域问题 -http.cors.enabled: true -http.cors.allow-origin: "*" -``` -再次启动`elasticsearch`,会发现`head`可以正常连接到ES了。 - -![head插件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4d718cf7b7bba9f61c67cd6f7958b8df.png) - -#### 方式3:使用chrome扩展插件 -此种方式安装的`head`插件,安装简单,没有跨域问题,但是需要可以上`外网`。 - -1. 打开chrome插件地址 [Elasticsearch Head](https://chrome.google.com/webstore/detail/elasticsearch-head/ffmkiejjmecolpfloofpjologoblkegm/) - -2. 点击添加至Chrome - ![Chrome head安装](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/36fc267332d7fa3aeb1a49fb651c48bc.png) -3. 等待安装完毕 -4. 点击chrome扩展中的head图标运行 -![Chrome-Head插件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4e8dec7a1910de042607676b3e475796.png) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/elasticsearch/elasticsearch-query.md b/docs/elasticsearch/elasticsearch-query.md deleted file mode 100644 index 19b9e65..0000000 --- a/docs/elasticsearch/elasticsearch-query.md +++ /dev/null @@ -1,288 +0,0 @@ ---- -title: 全文搜索ElasticSearch(二)深入搜索 -date: 2018-04-27 15:14:17 -url: lucene/elasticsearch-query -tags: -- Elasticsearch -categories: -- Elasticsearch ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![全文搜索](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/534fa087bb57ff07a93d9766d1a1b8fa.jpg) -现在,我们已经学会了如何使用 Elasticsearch 作为一个简单的 NoSQL 风格的分布式文档存储系统。我们可以将一个 JSON 文档扔到 Elasticsearch 里,然后根据 ID 检索。但 Elasticsearch 真正强大之处在于可以从无规律的数据中找出有意义的信息——从“大数据”到“大信息”。 - -Elasticsearch 不只会存储(stores) 文档,为了能被搜索到也会为文档添加索引(indexes) ,这也是为什么我们使用结构化的 JSON 文档,而不是无结构的二进制数据。 - -文档中的每个字段都将被索引并且可以被查询 。不仅如此,在简单查询时,Elasticsearch 可以使用 所有(all) 这些索引字段,以惊人的速度返回结果。这是你永远不会考虑用传统数据库去做的一些事情。 - - -笔者使用的ElasticSearch版本:**elasticsearch-5.5.1** -## 数据准备 -```json -{"title":"超级玛丽秘籍","author":"王五","word_count":5000,"publish_date":"1994-11-01"} -{"title":"三国志","author":"陈寿","word_count":30000,"publish_date":"1995-11-01"} -{"title":"三国演绎","author":"罗贯中","word_count":30000,"publish_date":"1994-03-01"} -{"title":"玛丽与管道工","author":"玛丽","word_count":10000,"publish_date":"1994-03-01"} -{"title":"史记","author":"司马迁","word_count":20000,"publish_date":"1994-08-01"} -{"title":"如何蹦的更高","author":"玛丽","word_count":20000,"publish_date":"1994-08-01"} -``` - -## 子条件查询 - -### Query context -在查询过程中,除了判断文档是否满足查询条件外,ES还会计算一个_score来标识匹配的程度,旨在判断目标文档和查询条件匹配的**有多好**。 - -常用查询: -全文本查询,针对**文本类型数据** - -#### 全文本查询-模糊查询 - -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "match":{ - "title":"三国志" - } - } -} -// 响应 -{ - "took": 4, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "failed": 0 - }, - "hits": { - "total": 2, - "max_score": 0.99938464, - "hits": [ - { - "_index": "book", - "_type": "novel", - "_id": "AWLOAB8bQcKhhHA2xN60", - "_score": 0.99938464, - "_source": { - "title": "三国志", - "author": "陈寿", - "word_count": 30000, - "publish_date": "1995-11-01" - } - }, - { - "_index": "book", - "_type": "novel", - "_id": "AWLOADU4QcKhhHA2xN61", - "_score": 0.34450945, - "_source": { - "title": "三国演绎", - "author": "罗贯中", - "word_count": 30000, - "publish_date": "1994-03-01" - } - } - ] - } -} -``` - -#### 全文本查询-短语查询 -查询书籍名字就是”三国志”的书籍信息。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "match_phrase":{ - "title":"三国志" - } - } -} - -``` - -#### 全文本查询-多字段查询 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "multi_match":{ - "query":"玛丽", - "fields":["author","title"] - } - } -} -``` -#### 全文本查询-语法查询 -查询“三国”和”演绎”同时匹配的书籍信息或者可以匹配“超级玛丽”的书籍信息。 - -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "query_string":{ - "query":"(三国 AND 演绎) OR 超级玛丽" - } - } -} -``` - -指定字段的语法查询,查询标题中包含三国或者玛丽的书籍信息。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "query_string":{ - "query":"三国 OR 玛丽", - "fields":["title"] - } - } -} -``` - -字段级别查询,针对结构化数据,如数字,日期等 -#### 字段级别查询-精确查询 -查询书籍字数为10000的书籍信息。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "term":{ - "word_count":10000 - } - } -} -``` - -#### 字段级别查询-范围查询 -查询书籍字数大于等于10000且小于等于20000的书籍信息,gte中的e是equals,等于的意思。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "range":{ - "word_count":{ - "gte":10000, - "lte":20000 - } - } - } -} -``` - - -### Filter context -在查询的过程中,只判断该文档是否满足条件,只有YES或者NO的结果。 -使用Filter查询书籍字数为10000的书籍信息。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "bool":{ - "filter":{ - "term":{ - "word_count":10000 - } - } - } - } -} -``` - -## 复合条件查询 - -### 固定分数查询 -"boost":2用于指定查询出来的信息评分都为2。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "constant_score":{ - "filter":{ - "match":{ - "title":"三国" - } - }, - "boost":2 - } - } -} -``` -### 组合或关系查询 -查询作者是陈寿或者标题为三国志的书籍信息。 -should表示条件之间是或的关系。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "bool":{ - "should":[ - { - "match":{ - "author":"陈寿" - } - }, - { - "match":{ - "title":"三国志" - } - } - ] - } - } -} -``` -### 组合与关系查询 -查询作者是陈寿或者标题为三国志的书籍信息。 -Must表示条件之间是且的关系。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "bool":{ - "must":[ - { - "match":{ - "author":"陈寿" - } - }, - { - "match":{ - "title":"三国志" - } - } - ] - } - } -} -``` - -### 组合非关系查询 -查询作者不是陈寿的书籍信息。 -```json -POST http://localhost:9200/book/_search -{ - "query":{ - "bool":{ - "must_not":{ - "match":{ - "author":"陈寿" - } - } - } - } -} -``` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/elasticsearch/solr-install.md b/docs/elasticsearch/solr-install.md deleted file mode 100644 index 50ade55..0000000 --- a/docs/elasticsearch/solr-install.md +++ /dev/null @@ -1,204 +0,0 @@ ---- -title: Solr7.3.0入门教程,部署Solr到Tomcat,配置Solr中文分词器 -date: 2018-04-13 17:31:11 -url: lucene/solr-install -tags: -- Solr -- 中文分词 -categories: -- Elasticsearch ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -### solr 基本介绍 - -Apache Solr (读音: SOLer) 是一个开源的搜索服务器。Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。Apache Solr 中存储的资源是以 Document 为对象进行存储的。每个文档由一系列的 Field 构成,每个 Field 表示资源的一个属性。Solr 中的每个 Document 需要有能唯一标识其自身的属性,默认情况下这个属性的名字是 id,在 Schema 配置文件中使用:id进行描述。 -Solr是一个高性能,采用Java开发,基于Lucene的全文搜索服务器。文档通过Http利用XML加到一个搜索集合中。查询该集合也是通过 http收到一个XML/JSON响应来实现。它的主要特性包括:高效、灵活的缓存功能,垂直搜索功能,高亮显示搜索结果,通过索引复制来提高可用性,提 供一套强大Data Schema来定义字段,类型和设置文本分析,提供基于Web的管理界面等。 - - - -#### solr 名称来源 -Search On Lucene Replication - -#### solr 历史 -2004年 CNET 开发 Solar,为 CNET 提供站内搜索服务 -2006年1月捐献给 Apache ,成为 Apache 的孵化项目 -一年后 Solr 孵化成熟,发布了1.2版,并成为 Lucene 的子项目 -2010年6月,solr 发布了的1.4.1版,这是1.4的 bugfix 版本,1.4.1的solr使用的lucene是2.9版本的 -solr 从1.4.x版本以后,为了保持和lucene同步的版本,solr直接进入3.0版本。 - -### 环境准备 -1. 下载JDK8 安装且配置环境变量 - 可以直接在[JDK官方网站](http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html)进行下载。 - 安装完毕之后[配置环境变量](https://jingyan.baidu.com/article/c85b7a6414f2ee003bac95d5.html)。 - -2. 下载Solr7.3.0并解压 - 可以直接在[Solr官方网站](https://lucene.apache.org/whoweare.html)进行下载。 - - ![Solr下载](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/fe144bc80396e60b15916c7e51aa7b13.jpg) -​ http://mirrors.hust.edu.cn/apache/lucene/solr/ - -3. 下载Tomcat 8或者 Tomcat 9 并解压 - 可以直接在[Tomcat官方网站](https://tomcat.apache.org/download-90.cgi)进行下载安装。 - https://tomcat.apache.org/download-90.cgi - - ![Tomcat下载](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/52e69f0e2689f1c02fe0224e27b8c4cc.jpg) - -### 部署Solr到Tomcat -说明: 我的解压路径 -``` -#Solr:D:\develop\solr-7.3.0 -#Tomcat:D:\webserver\apache-tomcat-9.0.1 -``` -#### 复制Solr文件到Tomcat -1. 拷贝文件夹 -D:\develop\solr-7.3.0\server\solr-webapp\webapp 到 -D:\webserver\apache-tomcat-9.0.1\webapps\ 下,并且重命名为`solr7.3` -2. 复制D:\develop\solr-7.3.0\server\lib\ext 下的所有Jar包到 -D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\lib -3. 复制D:\develop\solr-7.3.0\dist下的 - `solr-dataimporthandler-7.3.0.jar` - `solr-dataimporthandler-extras-7.3.0.jar ` -到D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\lib -4. 复制D:\develop\solr-7.3.0\server\lib下的`metrics-`开头的五个Jar -到D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\lib -5. 复制D:\develop\solr-7.3.0\server\resources\log4j.properties到 -D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\classes -#### 配置Solr Home -1. 新建文件夹D:\develop\solr-7.3.0-home,复制D:\develop\solr-7.3.0\server\solr下的所有内容到D:\develop\solr-7.3.0-home。 -2. 打开D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\web.xml -在47行配置SolrHome,用于指定数据存放位置。 -``` - - solr/home - D:\develop\solr-7.3.0-home - java.lang.String - -``` -#### 关闭安全约束 -注释掉D:\webserver\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\web.xml最下方的安全约束,让项目启动之后可以正常访问。 - -```xml - - -``` -#### Tomcat启动访问 -至此,Solr配置以及完成,启动Tomcat访问 -http://localhost:8080/solr7.3/index.html - -![Solr7.3首页](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1f414f595a368a2f1250a068c3f28c92.jpg)) - -### 创建Solr Core -点击网页左侧菜单Core Admin -输入 -​ name:blog -​ instanceDir:blog -点击`Add Core`,网页上方提示在D:\develop\solr-7.3.0-home\blog下无法找到solrconfig.xml: - -``` -Error CREATEing SolrCore 'blog': Unable to create core [blog] Caused by: Can't find resource 'solrconfig.xml' in classpath or 'D:\develop\solr-7.3.0-home\blog' -``` -我们复制官方给出的默认配置,复制 -D:\develop\solr-7.3.0-home\configsets\_default\conf -到D:\develop\solr-7.3.0-home\blog下,回到页面,再次点击`Add Core`,等待页面添加成功自动刷新。 -此时,在SolrHome下的blog文件夹下生成了相关文件: - -![Solr core](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/59449d149b6083c5f97b0e928c65661d.jpg) - -### 添加 Core 字段 -#### 在页面上添加 -打开http://localhost:8080/solr7.3/index.html选择左侧菜单`Core Selector` 选择刚才添加的blog,接着选择下面的Schema。可以在出现的页面上填写字段信息name:title,field type:string,然后点击`Add Field`完成添加。 -[Solr中的Field、CopyField、DynamicField与其他Field](https://blog.csdn.net/u011518678/article/details/51871925) -#### 在配置文件中添加 -我们打开文件: -D:\develop\solr-7.3.0-home\blog\conf\managed-schema -搜索title关键词,搜索到如下信息: -``` - -``` -可见,我们刚才在页面添加的字段信息最终会生成此条配置,因此,我们也可以拷贝这个配置更改name值为content就可以完成content字段的添加。 - -配置完成之后点击页面`Core Admin `然后点击`Reload`刷新信息,我们就可以在Schema中查看到content字段。 - -### 添加/更新/查询数据 - -在页面选择blog Core,选择Documents,在Document(s)中输入下面内容进行数据然后点击Submit Document添加数据,如果ID已经存在,则为更新。如果JSON中没有写ID字段,会随机生成ID。 - -``` -{ - "id": "10000", - "title": "Sorl入门教程", - "content": "sorl下载,solr配置,solr启动" -} -``` -提交之后可以看到成功添加信息的反馈。 - -``` -Status: success -Response: -{ - "responseHeader": { - "status": 0, - "QTime": 4 - } -} -``` - -数据已经成功添加,如果需要查询数据可以点击左侧`Query`,点击`Execute Query`查询出刚才添加的数据。 - - -### 中文分词器的使用 - -在已经运行的页面上,选择blog Core,然后选择`Analysis`,输入中国人民,字段类型选择_text_,点击`Analyse Values`可以看到没有中文分词器的结果: - -![没有分词器](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/90dabacb5dbcf838003076e24c6ba71e.jpg) - -下面使用中文分词器,下载ikanalyzer-solr6.5 -解压将`IKAnalyzer.cfg.xml`以及`stopword.dic`解压到 -\apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\classes - - 解压将里面的两个jar包`ik-analyzer-solr5-5.x.jar`以及`solr-analyzer-ik-5.1.0.jar`解压到 - \apache-tomcat-9.0.1\webapps\solr7.3\WEB-INF\lib - - 打开刚才创建的blog core目录\solr-7.3.0-home\blog\conf,编辑managed-schema文件添加配置: - -``` - - - -``` - -在页面选择Core Admin -> blog -> Reload 刷新配置,然后选择blog Core,然后选择`Analysis`,输入中国人民,字段类型选择text_ik,点击`Analyse Values`可以看到没有中文分词器的结果: - -![使用分词器](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/a57faf42fa5e96435d4ffde6b8a66c0b.jpg) - - -### 参考资料 -[Solr官方文档](https://lucene.apache.org/solr/guide/7_3/solr-tutorial.html) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/htmlcss/css-mac-window.md b/docs/htmlcss/css-mac-window.md deleted file mode 100644 index 62bda8d..0000000 --- a/docs/htmlcss/css-mac-window.md +++ /dev/null @@ -1,95 +0,0 @@ ---- -title: CSS - 使用CSS实现Mac窗口效果 -date: 2018-08-15 05:42:55 -updated: 2018-08-15 05:42:55 -url: html-css/css-mac-window -categories: -- 前端开发 -tags: -- Css -- Html ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -来不及了,直接贴代码。 -### HTML代码 - -```html -
-
-
-
-
-
-
- Wait,i know you
- keep hungry keep foolish
- 苍苍之天不可得久视,堂堂之地不可得久履,道此绝矣!
- 告后世及其孙子,忽忽锡锡,恐见故里,毋负天地,
- 更亡更在,去如舍庐,下敦闾里!人固当死,慎毋敢佞。 -
-
-``` - -### CSS代码 -主要是三个圆点 -```css -.user-bio { - font-size: 14px; - color: #6a737d; -} -.user-bio .user-bio-top { - height: 15px; - background-color: #E3E3E3; - padding: 8px 12px; - border-top-left-radius: 3px; - border-top-right-radius: 3px; -} -.user-bio .user-bio-body { - background-color: #EEEEEE; - width: 100%; - font-size: 13px; - color: #666666; - overflow: auto; - padding: 20px; - font-family: "Source Code Pro", Consolas, Menlo, Monaco, "Courier New", monospace; -} -.user-bio .user-bio-top div{ - float: left; - margin-right: 10px; - width:13px; - height:13px; - background-color:#FF5F57; - border-radius:50px; -} -.user-bio .user-bio-top .circle-yellow{ - float: left; - margin-right: 10px; - width:13px; - height:13px; - background-color:#FFBD2E; - border-radius:50px; -} -.user-bio .user-bio-top .circle-green{ - float: left; - margin-right: 10px; - width:13px; - height:13px; - background-color:#28CA42; - border-radius:50px; -} -``` -### 最终效果 -![MAC-Window-css](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1f2dd02d503fcd77d79e41a208349c59.jpg) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/htmlcss/jquery-easing-anchor-animation.md b/docs/htmlcss/jquery-easing-anchor-animation.md deleted file mode 100644 index e92ee26..0000000 --- a/docs/htmlcss/jquery-easing-anchor-animation.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: Jquery - 使用jquery-easing.js实现页面锚点平滑滚动 -date: 2018-08-19 08:48:03 -updated: 2018-08-19 08:48:03 -url: html-css/jquery-easing-anchor-animation -categories: -- 前端开发 -tags: -- Css -- Javascript -- 平滑滚动 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -### 使用jquery-easing.js实现页面锚点平滑滚动 - -使用[jquery easing.js](http://gsgd.co.uk/sandbox/jquery/easing/jquery.easing.1.3.js "jquery easing.js")可以实现多动动画效果。 -jquery easing.js中定义了很多种不同的动画效果,动画速度和时间的关系图可以参考此图: - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/f81d0b4842589fcbd4d085cdd7cd64b1.jpg) - - - -使用easeInOutExpo效果示例锚点的平滑滚动效果,直接上代码。 - -### 效果图 - -![平滑滚动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4176b134e77e84d73d0fceb55b96b521.gif) - -### HTML代码 -```html - - - - - - 平滑滚动 - - -

1

-

2

-

3

-

4

-

5

-

6

-

7

-

8

-

9

-

10

-

11

-

12

-

13

-

14

-

15

-

16

-

17

-

18

-

19

-

20

-

21

-

22

-

23

-

24

-

25

-

26

-

27

-

28

-

29

-

30

-

31

-
TO P1 - - - - - - - -``` - - -### Anchor-animation.js代码 -```javascript -(function($) { - // Smooth scrolling using jQuery easing - $('a[href*="#"]:not([href="#"])').click(function() { - if (location.pathname.replace(/^\//, '') == this.pathname.replace(/^\//, '') && location.hostname == this.hostname) { - var target = $(this.hash); - target = target.length ? target : $('[name=' + this.hash.slice(1) + ']'); - if (target.length) { - $('html, body').animate({ - scrollTop: target.offset().top - }, 1000, "easeInOutExpo"); - return false; - } - } - }); -})(jQuery); // End of use strict -``` - -### jquery-easing.js代码 -此处因为只使用到了easeInOutExpo方法,所以我删除了其他没有用到的方法,只保留了easeInOutExpo方法。 - -```javascript -jQuery.easing['jswing'] = jQuery.easing['swing']; - -jQuery.extend( jQuery.easing, -{ - def: 'easeOutQuad', - swing: function (x, t, b, c, d) { - //alert(jQuery.easing.default); - return jQuery.easing[jQuery.easing.def](x, t, b, c, d); - }, - - easeInOutExpo: function (x, t, b, c, d) { - if (t==0) return b; - if (t==d) return b+c; - if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b; - return c/2 * (-Math.pow(2, -10 * --t) + 2) + b; - } -}); - - -``` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/java/java-load-balancing.md b/docs/java/java-load-balancing.md deleted file mode 100644 index 3ec146f..0000000 --- a/docs/java/java-load-balancing.md +++ /dev/null @@ -1,280 +0,0 @@ ---- -title: 一篇有趣的负载均衡算法实现 -date: 2020-05-29 07:37:19 -url: algorithm/load-balancing -categories: -- Java 开发 -tags: -- 负载均衡 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -**负载均衡**(Load balancing)是一种在多个计算机(网络、CPU、磁盘)之间均匀分配资源,以提高资源利用的技术。使用负载均衡可以最大化服务吞吐量,可能最小化响应时间,同时由于使用负载均衡时,会使用多个服务器节点代单点服务,也提高了服务的可用性。 - -负载均衡的实现可以软件可以硬件,硬件如大名鼎鼎的 F5 负载均衡设备,软件如 NGINX 中的负载均衡实现,又如 Springcloud Ribbon 组件中的负载均衡实现。 - -如果看到这里你还不知道负载均衡是干嘛的,那么只能放一张图了,毕竟没图说个啥。 - -![正经的负载均衡示例](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200527075734086.png) - -负载均衡要做到在多次请求下,每台服务器被请求的次数大致相同。但是实际生产中,可能每台机器的性能不同,我们会希望性能好的机器承担的请求更多一些,这也是正常需求。 - -如果这样说下来你看不懂,那我就再举个例子好了,一排可爱的小熊(服务器)站好。 - -![一排要被访问的服务器](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200527082251915.png) - -这时有人(用户)要过来打脸(请求访问)。 - -![用户请求](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200527082442214.png) - -那么怎么样我们才能让这每一个可爱的小熊被打的次数大致相同呢? - -又或者熊 4 比较胖,抗击打能力是别人的两倍,我们怎么提高熊 4 被打的次数也是别人的两倍呢? - -又或者每次出手的力度不同,有重有轻,恰巧熊 4 总是承受这种大力度啪啪打脸,熊 4 即将不省熊事,还要继续打它吗? - -这些都是值的思考的问题。 - -**说了那么多,口干舌燥,我双手已经饥渴难耐了,迫不及待的想要撸起代码了。** - -## 1. 随机访问 - -上面说了,为了负载均衡,我们必须保证多次出手后,熊 1 到熊 4 被打次数均衡。比如使用随机访问法,根据数学上的概率论,随机出手次数越多,每只熊被打的次数就会越相近。代码实现也比较简单,使用一个随机数,随机访问一个就可以了。 - -```java -/** 服务器列表 */ -private static List serverList = new ArrayList<>(); -static { - serverList.add("192.168.1.2"); - serverList.add("192.168.1.3"); - serverList.add("192.168.1.4"); - serverList.add("192.168.1.5"); -} - -/** - * 随机路由算法 - */ -public static String random() { - // 复制遍历用的集合,防止操作中集合有变更 - List tempList = new ArrayList<>(serverList.size()); - tempList.addAll(serverList); - // 随机数随机访问 - int randomInt = new Random().nextInt(tempList.size()); - return tempList.get(randomInt); -} -``` - -因为使用了非线程安全的集合,所以在访问操作时操作的是集合的拷贝,下面几种轮询方式中也是这种思想。 - -写一个模拟请求方法,请求10w次,记录请求结果。 - -```java -public static void main(String[] args) { - HashMap serverMap = new HashMap<>(); - for (int i = 0; i < 20000; i++) { - String server = random(); - Integer count = serverMap.get(server); - if (count == null) { - count = 1; - } else { - count++; - } - // 记录 - serverMap.put(server, count); - } - // 路由总体结果 - for (Map.Entry entry : serverMap.entrySet()) { - System.out.println("IP:" + entry.getKey() + ",次数:" + entry.getValue()); - } -} -``` - -运行得到请求结果。 - -``` -IP:192.168.1.3,次数:24979 -IP:192.168.1.2,次数:24896 -IP:192.168.1.5,次数:25043 -IP:192.168.1.4,次数:25082 -``` - -每台服务器被访问的次数都趋近于 2.5w,有点负载均衡的意思。但是随机毕竟是随机,是不能保证访问次数绝对均匀的。 - -## 2. 轮询访问 - -轮询访问就简单多了,拿上面的熊1到熊4来说,我们一个接一个的啪啪 - 打脸,熊1打完打熊2,熊2打完打熊3,熊4打完打熊1,最终也是实现了被打均衡。但是保证均匀总是要付出代价的,随机访问中需要随机,轮询访问中需要什么来保证轮询呢? - -```java -/** 服务器列表 */ -private static List serverList = new ArrayList<>(); -static { - serverList.add("192.168.1.2"); - serverList.add("192.168.1.3"); - serverList.add("192.168.1.4"); - serverList.add("192.168.1.5"); -} -private static Integer index = 0; - -/** - * 随机路由算法 - */ -public static String randomOneByOne() { - // 复制遍历用的集合,防止操作中集合有变更 - List tempList = new ArrayList<>(serverList.size()); - tempList.addAll(serverList); - String server = ""; - synchronized (index) { - index++; - if (index == tempList.size()) { - index = 0; - } - server = tempList.get(index);; - } - return server; -} -``` - -由代码里可以看出来,为了保证轮询,必须记录上次访问的位置,为了让在并发情况下不出现问题,还必须在使用位置记录时进行加锁,很明显这种互斥锁增加了性能开销。 - -依旧使用上面的测试代码测试10w次请求负载情况。 - -```java -IP:192.168.1.3,次数:25000 -IP:192.168.1.2,次数:25000 -IP:192.168.1.5,次数:25000 -IP:192.168.1.4,次数:25000 -``` - -## 3. 轮询加权 - -上面演示了轮询方式,还记的一开始提出的熊4比较胖抗击打能力强,可以承受别人2倍的挨打次数嘛?上面两种方式都没有体现出来熊 4 的这个特点,熊 4 窃喜,不痛不痒。但是熊 1 到 熊 3 已经在崩溃的边缘,不行,我们必须要让胖着多打,能者多劳,提高整体性能。 - -```java -/** 服务器列表 */ -private static HashMap serverMap = new HashMap<>(); -static { - serverMap.put("192.168.1.2", 2); - serverMap.put("192.168.1.3", 2); - serverMap.put("192.168.1.4", 2); - serverMap.put("192.168.1.5", 4); -} -private static Integer index = 0; - -/** - * 加权路由算法 - */ -public static String oneByOneWithWeight() { - List tempList = new ArrayList(); - HashMap tempMap = new HashMap<>(); - tempMap.putAll(serverMap); - for (String key : serverMap.keySet()) { - for (int i = 0; i < serverMap.get(key); i++) { - tempList.add(key); - } - } - synchronized (index) { - index++; - if (index == tempList.size()) { - index = 0; - } - return tempList.get(index); - } -} -``` - -这次记录下了每台服务器的整体性能,给出一个数值,数值越大,性能越好。可以承受的请求也就越多,可以看到服务器 `192.168.1.5` 的性能为 4,是其他服务器的两倍,依旧 10 w 请求测试。 - -```java -IP:192.168.1.3,次数:20000 -IP:192.168.1.2,次数:20000 -IP:192.168.1.5,次数:40000 -IP:192.168.1.4,次数:20000 -``` - - `192.168.1.5` 承担了 2 倍的请求。 - -## 4. 随机加权 - -随机加权的方式和轮询加权的方式大致相同,只是把使用互斥锁轮询的方式换成了随机访问,按照概率论来说,访问量增多时,服务访问也会达到负载均衡。 - -```java -/** 服务器列表 */ -private static HashMap serverMap = new HashMap<>(); -static { - serverMap.put("192.168.1.2", 2); - serverMap.put("192.168.1.3", 2); - serverMap.put("192.168.1.4", 2); - serverMap.put("192.168.1.5", 4); -} -/** - * 加权路由算法 - */ -public static String randomWithWeight() { - List tempList = new ArrayList(); - HashMap tempMap = new HashMap<>(); - tempMap.putAll(serverMap); - for (String key : serverMap.keySet()) { - for (int i = 0; i < serverMap.get(key); i++) { - tempList.add(key); - } - } - int randomInt = new Random().nextInt(tempList.size()); - return tempList.get(randomInt); -} -``` - -依旧 10 w 请求测试,`192.168.1.5` 的权重是其他服务器的近似两倍, - -```log -IP:192.168.1.3,次数:19934 -IP:192.168.1.2,次数:20033 -IP:192.168.1.5,次数:39900 -IP:192.168.1.4,次数:20133 -``` - -## 5. IP-Hash - -上面的几种方式要么使用随机数,要么使用轮询,最终都达到了请求的负载均衡。但是也有一个很明显的缺点,就是同一个用户的多次请求很有可能不是同一个服务进行处理的,这时问题来了,如果你的服务依赖于 session ,那么因为服务不同, session 也会丢失,不是我们想要的,所以出现了一种根据请求端的 ip 进行哈希计算来决定请求到哪一台服务器的方式。这种方式可以保证同一个用户的请求落在同一个服务上。 - -```java -private static List serverList = new ArrayList<>(); -static { - serverList.add("192.168.1.2"); - serverList.add("192.168.1.3"); - serverList.add("192.168.1.4"); - serverList.add("192.168.1.5"); -} - -/** - * ip hash 路由算法 - */ -public static String ipHash(String ip) { - // 复制遍历用的集合,防止操作中集合有变更 - List tempList = new ArrayList<>(serverList.size()); - tempList.addAll(serverList); - // 哈希计算请求的服务器 - int index = ip.hashCode() % serverList.size(); - return tempList.get(Math.abs(index)); -} -``` - -## 6. 总结 - -上面的四种方式看似不错,那么这样操作下来真的体现了一开始说的负载均衡吗?答案是不一定的。就像上面的最后一个提问。 - -> 又或者每次出手的力度不同,有重有轻,恰巧熊 4 总是承受这种大力度啪啪打脸,熊 4 即将不省熊事,还要继续打它吗? - -服务器也是这个道理,每次请求进行的操作对资源的消耗可能是不同的。比如说某些操作它对 CPU 的使用就是比较高,也很正常。**所以负载均衡有时不能简单的通过请求的负载来作为负载均衡的唯一依据**。还可以结合服务的当前连接数量、最近响应时间等维度进行总体均衡,总而言之,就是为了达到资源使用的负载均衡。 - - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/java/java-think-code-standards.md b/docs/java/java-think-code-standards.md deleted file mode 100644 index f4d3338..0000000 --- a/docs/java/java-think-code-standards.md +++ /dev/null @@ -1,159 +0,0 @@ ---- -title: Java 开发的编程噩梦,这些坑你没踩过算我输 -date: 2020-08-07 08:01:01 -url: java/java-code-standards -tags: - - 开发技巧 -categories: - - Java 开发 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -很多 Java 初学者在开始编程时会出现一些问题,这些问题并不是指某个特定领域的问题,也不是指对某个业务不熟悉而导致的问题,而是对基础知识不够熟悉导致的问题。而就是这些问题让我们编写了一些不够健壮的代码。 -这篇文章会列举几种编程初学者常常出现的一些问题,我相信这些问题多多少少也曾困扰着现在或曾经的你。如果觉得文章不错,不妨点赞分享,让更多人跳过这些开发中的坑。 - -## 随处可见的 Null 值 -我见过很多的代码会把 Null 值作为返回值,当你预期是一个字符串时,意外得到了一个 Null 值;当你预期得到一个 List 时,意外又得到了一个 Null 值,如果你不进行处理,那么你还会意外得到 `NullPointerException`. -就像下面这样。 - -```java -// 情况1 -String userTag = getUserTag(); -if (userTag.equals("admin")) { // NullPointerException - // ... -} - -// 情况2 -List carList = getCarList(); -for (String car : carList) { // NullPointerException - // ... -} - -``` -为了防止这种情况,你可以在 List 返回时给出一个空的集合而不是 Null,如果是字符串,你可以把要确定有值对象放在比较的前面。 - -```java -if ("admin".equals(userTag)) { - // ... -} -// 或者 -if (Objects.equals(userTag,"admin")){ - // ... -} -``` - -## 没有进行空值检查 -可能你考虑到了上面的 Null 值情况,但是在实际处理时没有考虑空值情况,比如字符空串空串 "",或者集合为空。那么在后续处理时又有可能得到一个 `NullPointerException`. 所以你应该进行空值判断。 -```java -String userTag = getUserTag(); -if (userTag != null && userTag.trim() != "") { - // ... -} - -List carList = getCarList(); -if (carList != null && !carList.isEmpty()) { - // ... -} -``` -## 忽略的异常处理 -异常处理总是一件烦人的事,而忽略异常似乎总有一种吸引人的魔力。我见过像下面这样的代码。 - -```java -try { - List result= request(); - // ... -}catch (Exception e){ - -} -``` - -你没有看错,catch 中没有任何内容,后来出现了问题,看着日志文件一片太平无迹可寻。异常是故意抛出来的,你应该正确处理它们或者继续抛出。而且同时,你该输出一行日志用来记录这个异常,方便以后的问题追踪。 - -## 没有释放资源 - -在读取文件或者请求网络资源时,总是需要进行 close 操作,这很重要,否则可能会阻塞其他线程的使用。但是初学者可能会忘记这一步操作。其实在 Java 7 开始,就提供了 `try-with-resources` 自动关闭资源的特性,只需要把打开的资源放入 `try` 中。 - -```java -try (FileReader fileReader = new FileReader("setting.xml")) { - // fileReader.read(); - // ... -} catch (Exception e) { - e.printStackTrace(); -} -``` -像上面这样,不需要在 `finally` 里手动调用 `fileReader` 的 `close` 方法关闭资源,因为放在 `try` 里的资源调用会在使用完毕时自动调用 `close`. 而且不管是否有异常抛出,这很实用。 - -## ConcuretModificationException - -总有一天你会遇到 `ConcuretModificationException` ,然后开始百度搜索它的解决方式,这个异常最常见的场景是你在遍历一个集合时进行更新操作,比如像下面这样。 -```java -List list = new ArrayList<>(); -list.add("a1"); -list.add("b1"); -list.add("b2"); -list.add("c1"); -for (String s : list) { - if ("b1".equals(s)) { - list.remove(s); - } -} -``` - -这个异常很有用处,因为 ArrayList 不是线程安全的集合,假设你这边一边遍历,另一个线程不断更新,非线程安全集合会导致你的遍历结果不正确,所以这个异常的存在是合理的。同理 HashMap 也是如此,关于 HashMap 之前已经有一篇文章详细介绍了,可以参考 [最通俗易懂的 HashMap 源码分析解读](https://mp.weixin.qq.com/s/q-rWq79HmzPe08gyfOjaIA)。 - -## 缺少注释 - -**准确的注释可以救人于水火**,这点有时候一点也不夸张。虽然说优秀的代码本身就是非常好的注释,但是这实际开发起来,很少发生。注释并不需要你事无巨细的一一记录,但是你该在核心逻辑添加应有的注释,比如复杂逻辑的实现思路,当前逻辑业务需求。某个判断的添加原因,某个异常的发生情况等等。这可以让你在未来的某一天需要回看现在的代码时感谢自己。更可以**让你在某天的甩锅中轻松胜出**。 - -## 不进行代码测试 - -我见过有些同事在功能开发完毕后直接扔给对接同事使用,而自己却没有经过任何测试,或者只是测试了某个简单的情况。测试是开发过程中的重要环节,没有经过严格测试的代码很难说没有问题,我觉得在功能开发完毕后至少需要**单元测试**,**特殊用例**测试,**集成测试**以及其他形式的测试。**严格的测试不仅可以第一时间发现问题,更可以减少后面不必要的对接调试时间**。 - -## 重复造轮子 - -你知道的,Java 社区非常活跃,存在着大量的第三方类库,开源作者可能花费了数年时间去维护和完善类库,这些类库非常优秀。同时 JDK 也提供了大量的常用的功能封装。这些都可以**为我们的开发速度插上翅膀**。所以,当你需要一个功能时候,应该首先看下 JDK 和已经引入的类库中是否已经存在相同功能,而不是自己重复造轮子,而且大部分情况下你造的轮子还不如别人好。 - -下面举些例子。 - -- 你需要日志记录,可以使用 logback. -- 你需要网络操作,可以使用 netty. -- 你需要解析 JSON,可以使用 gson. -- 你需要解析表格,可以使用 apache poi. -- 你需要通用操作,可以使用 apache commons. - -另外一种情况是,你可能不知道某个功能在 JDK 中已经实现,这时候你应该多多查看 JDK Document. 我就在工作中见到过同事手写字符串 split,为了获取时间戳把 Date 对象转换到 Calendar. - -## 缺少必要的沟通 - -这个部分是和开发没有关系的,但是这个环节往往会影响最终的开发结果。进行具体的开发之前,你应该详细的沟通并理解功能的需求,这样你才能针对具体的需求写出不偏离实际需要的代码。有时候你很有可能因为缺少必要的沟通,错误了理解了需求,最终在开发完毕后发现自己写的功能完全没有用处。 - -## 没有代码规范 - -代码规范性非常重要,如果一个项目里充斥着各种稀奇古怪的代码规范,会让维护者十分头疼。而且软件行业高速发展,对开发者的综合素质要求也越来越高,优秀的编程习惯也可以提高软件的最终质量。比如:标新立异的命名风格挑战阅读习惯;五花八门的错误码人为地 增加排查问题的难度;工程结构混 乱导致后续项目维护艰难;没有鉴权的漏洞代码易被黑客攻击;质量低下的代码上线之后漏洞百出等等。因为没有统一的代码规范,开发中的问题可能层出不穷。 - -下面简单列举些应该统一的开发规范。如**命名风格如何是好;常量名称结构怎样;代码格式怎么统一;日期时间格式如何处理;集合处理注意事项;日志打印有无规范;前后交互具体规约**等。 - -上面所说的开发规范代码规范推荐阿里推出的 《**Java 开发手册**》,里面详细列举了在 Java 开发中各个方面应该遵守的规约和规范。最新版本在 8月 3日已经发布,可以在公众号 “**未读代码**” 直接回复 "java " **获取最新版 pdf.** - -## 总结 - -Bug 和技术上的误解都是美丽的谜团,福尔摩斯般的我们终将解决这些问题。命运自己掌握,每一次探清的这些技术误解,都会增加我们对开发编码的理解。**尽情接招吧,色彩斑斓才有趣,万般体验才是人生,不管是多样的技术,还是多样的问题,我都想看见。** - - - -**参考:** - -[1] [A beginner’s guide to Java programming nightmares](https://jaxenter.com/java-programming-nightmares-156749.html) - -[2] [Java™ Platform, Standard Edition 8 API Specification](https://docs.oracle.com/javase/8/docs/api/) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-07-feature.md b/docs/jdk/java-07-feature.md deleted file mode 100644 index a650b6a..0000000 --- a/docs/jdk/java-07-feature.md +++ /dev/null @@ -1,462 +0,0 @@ ---- -title: 还看不懂同事代码?快来补一波 Java 7 语法特性 -# toc: false -date: 2020-01-08 08:01:01 -url: jdk/jdk7-start -tags: - - Java7 - - AutoCloseable -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -# 前言 - -Java 平台自出现到目前为止,已经 20 多个年头了,这 20 多年间 Java 也一直作为最流行的程序设计语言之一,不断面临着其他新兴编程语言的挑战与冲击。Java 语言是一种**静态强类型**语言,这样的语言特性可以让 Java 编译器在**编译阶段**发现错误,这对于构建出一个**稳定安全且健壮**的应用来说,尤为重要。但是也因为这种特性,让 Java 开发似乎变得缺少灵活性,开发某些功能的应用时,代码量可能是其他语言的几倍。Java 开发的不足之处也体现越来越复杂的 JDK 上,越来越复杂的 JDK 让开发者完全理解的难度变的非常大。以至于开发者有时会重复实现一个 JDK 中已经提供了的功能。 - -为了跟上互联网应用编程发展的脚步, Java 从 9 版本开始调整了 JDK 发布的节奏,JDK 的每次更新都注重**提高生产效率**,提高 **JVM 性能**,推行**模块化**等,让开发者可以更多的专注于业务本身,而不是浪费过多的时间在语言特性上。 Java 语言的更新要在语言的严谨性和灵活性上找到一个平衡点,毕竟灵活性可以减少编码的复杂度,而严谨性是构建复杂且健壮应用的基石。 - -# Java 7 语言特性 - -Java 重要的更新版本是在 Java 5 版本,这个版本中增加了如泛型、增强 for、自动装箱拆箱、枚举类型,可变参数、注解等一系列**重要功能**,但是随后的 Java 6 中并没有增加新的重要的语言特性。Java 5 的发布是在 2004 年,已经很久远了,网上关于 Java 的教程也大多是基于 Java 6 的,也因此我准备从 Java 7 开始介绍每个 Java 版本的新特性。 - -下面所有代码的运行演示都是基于 **Java 7 ** ,所以你如果尝试下面的代码,需要**安装并配置** Jdk 1.7 或者已上版本。 - -# 1. switch String - -在 Java 7 之前,switch 语法中只支持整数类型以及这些整数类型的封装类进行判断,在 Java 7 中,支持了 string 字符串类型的判断,使用起来非常的简单,但是实用性是很高的。 - -## 1.1. switch String 基本用法 - -编写一个简单的 switch 判断字符串的测试类。 - -```java -public class SwitchWithString { - - public static void main(String[] args) { - String gender = "男"; - System.out.println(gender.hashCode()); - switch (gender) { - case "男": - System.out.println("先生你好"); - break; - case "女": - System.out.println("女士你好"); - break; - default: - System.out.println("你好"); - } - } -} -``` - -switch 判断字符串使用起来很简单,结果也显而易见会先输出 gender 变量的 hashCode,然后输出匹配结果“先生你好”。 - -```java -30007 -先生你好 -``` - -在使用 switch string 时候,如果结合 Java 5 的**枚举类**,那么效果会更好,Java 7 之前使用 switch 结合枚举类要为每个枚举值编数字代号,Java 7 之后可以枚举进行 switch。 - -## 1.2. switch String 实现原理 - -但是这个支持**只是编译器层面的支持**, Java 虚拟机依旧是不支持的。在对字符串进行 switch 时,编译器会把字符串**转换成整数**类型再进行判断。为了验证上面说的只是编译器层面的支持,我们反编译(可以使用 Jad 反编译工具,也可以在 Idea 中双击编译生成的 class )生成的 class 文件,看到编译器把 switch string 转换成了字符串 hashCode 判断,为了防止 hashCode 冲突,又使用了 equals 再次判断。 - -```java -public class SwitchWithString { - public SwitchWithString() { - } - - public static void main(String[] args) { - String gender = "男"; - System.out.println(gender.hashCode()); - byte var3 = -1; - switch(gender.hashCode()) { - case 22899: - if (gender.equals("女")) { - var3 = 1; - } - break; - case 30007: - if (gender.equals("男")) { - var3 = 0; - } - } - - switch(var3) { - case 0: - System.out.println("先生你好"); - break; - case 1: - System.out.println("女士你好"); - break; - default: - System.out.println("你好"); - } - - } -} -``` - -# 2. try-with-resource - -Java 不同于 C++,需要开发者自己管理每一块内存,大多时候 Java 虚拟机都可以很好的帮我们进行资源管理,但是也有时候需要手动释放一些资源,比如数据库连接、磁盘文件连接、网络连接等。换句话说,只要是资源数量有限的,都需要我们手动的进行释放。 - -## 2.1. try-catch-finally - -在操作有限资源的时候,可能会出现各种异常,不管是读取阶段还是在最后关闭资源的过程中,都有可能出现问题,我们通常会使用下面的方式 `try-catch-finally` 保证资源的释放。 - -像下面这样。 - -```java -/** - * 释放资源 - * - * @author www.wdbyte.com - */ -public class TryCatachFinally { - - /** - * 异常处理 - * - * @param args - */ - public static void main(String[] args) throws Exception { - FileInputStream inputStream = null; - try { - inputStream = new FileInputStream("jdk-feature-7.iml"); - } catch (FileNotFoundException e) { - throw e; - } finally { - if (inputStream != null) { - try { - inputStream.close(); - } catch (IOException e) { - throw e; - } - } - } - } -} -``` - -看看这恶心的代码结构,为了捕获异常,我们写了一个 `catch`,为了能保证释放资源,我们又写了 `finally` 进行资源释放,在资源释放时为了捕捉 `close` 时抛出的异常,我们又写了一个 `try-catch`。最后看着这复杂的代码,如果有人告诉你这段代码有 `bug`,那你一定不会相信。但是确实是这样,看起来严密的代码逻辑,当 `try` 中的代码逻辑和 `close` 方法同时产生异常的时候,`try` 中的异常信息会丢失。 - -可以看这里例子。 - -```java -package net.codingme.feature.jdk7; - -import java.io.IOException; - -/** - * 释放资源 - * - * @author www.wdbyte.com - */ -public class TryCatachFinallyThrow { - - /** - * 异常处理 - * - * @param args - */ - public static void main(String[] args) throws Exception { - read(); - } - - public static void read() throws Exception { - FileRead fileRead = null; - try { - fileRead = new FileRead(); - fileRead.read(); - } catch (Exception e) { - throw e; - } finally { - if (fileRead != null) { - try { - fileRead.close(); - } catch (Exception e) { - throw e; - } - } - } - - } -} - -class FileRead { - - public void read() throws Exception { - throw new IOException("读取异常"); - } - - public void close() throws Exception { - System.out.println("资源关闭"); - throw new IOException("关闭异常"); - } -} -``` - -很明显代码里 `read` 和 `close` 方法都会产生异常,但是运行程序发现只能收到 `close` 的异常信息。 - -```log -资源关闭 -Exception in thread "main" java.io.IOException: 关闭异常 - at net.codingme.feature.jdk7.FileRead.close(TryCatachFinallyThrow.java:51) - at net.codingme.feature.jdk7.TryCatachFinallyThrow.read(TryCatachFinallyThrow.java:33) - at net.codingme.feature.jdk7.TryCatachFinallyThrow.main(TryCatachFinallyThrow.java:20) -``` - -**异常信息丢失**了,可怕的是你以为只是 `close` 时发生了异常而已。 - -## 2.2. try-autocloseable - -上面的问题在 Java 7 中其实已经提供了新的解决方式,Java 7 中对 `try` 进行了增强,可以保证资源**总能被正确释放** 。使用增强 `try` 的前提是 `try` 中的类实现了 `AutoCloseable` 接口,在 Java 7 中大量的需要释放资源的操作其实都已经实现了此接口了。 - -![AutoCloseable 实现类](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200107084113348.png) - -实现了 `AutoCloseable` 的类,在增强 `try`中使用时,不用担心资源的关闭,在使用完毕会自动的调用 `close`方法,并且**异常不会丢失**。 - -让我们编写的模拟资源操作的类实现 `AutoCloseable` 接口,然后时候增强 `try` 看看效果。 - -```java -package net.codingme.feature.jdk7; - -/** - * 自动关闭 - * - * @author www.wdbyte.com - */ -public class AutoCloseResource { - public static void main(String[] args) throws Exception { - try (Mysql mysql = new Mysql(); - OracleDatabase oracleDatabase = new OracleDatabase()) { - mysql.conn(); - oracleDatabase.conn(); - } - } -} - -class Mysql implements AutoCloseable { - - @Override - public void close() throws Exception { - System.out.println("mysql 已关闭"); - } - - public void conn() { - System.out.println("mysql 已连接"); - } -} - -class OracleDatabase implements AutoCloseable { - - @Override - public void close() throws Exception { - System.out.println("OracleDatabase 已关闭"); - } - - public void conn() { - System.out.println("OracleDatabase 已连接"); - } -} -``` - -测试类 Mysql 和 OracleDatabase 都是实现了 AutoCloseable,运行查看结果。 - -```java -mysql 已连接 -OracleDatabase 已连接 -OracleDatabase 已关闭 -mysql 已关闭 -``` - -确认在发生异常时候异常信息不会丢失,写一个有异常的模拟测试类进行测试。 - -```java -package net.codingme.feature.jdk7; - -import java.io.IOException; - -/** - * 释放资源 - * - * @author www.wdbyte.com - */ -public class AutoCloseThrow { - - public static void main(String[] args) throws Exception { - try (FileReadAutoClose fileRead = new FileReadAutoClose()) { - fileRead.read(); - } - } -} - -class FileReadAutoClose implements AutoCloseable { - - public void read() throws Exception { - System.out.println("资源读取"); - throw new IOException("读取异常"); - } - - @Override - public void close() throws Exception { - System.out.println("资源关闭"); - throw new IOException("关闭异常"); - } -} -``` - -运行查看异常信息。 - -```log -资源读取 -资源关闭 -Exception in thread "main" java.io.IOException: 读取异常 - at net.codingme.feature.jdk7.FileReadAutoClose.read(AutoCloseThrow.java:23) - at net.codingme.feature.jdk7.AutoCloseThrow.main(AutoCloseThrow.java:14) - Suppressed: java.io.IOException: 关闭异常 - at net.codingme.feature.jdk7.FileReadAutoClose.close(AutoCloseThrow.java:29) - at net.codingme.feature.jdk7.AutoCloseThrow.main(AutoCloseThrow.java:15) -``` - -自动关闭,异常清晰,关闭异常存在于 `Suppressed` ,称为抑制异常,后续文章会详细介绍。 - -# 3. try-catch - -在 Java 7 之前,一个 catch 只能捕获一个异常信息,当异常种类非常多的时候就很麻烦,但是在 Java 7 中,一个 catch 可以捕获多个异常信息,每个异常捕获之间使用 `|` 分割, - -```java -package net.codingme.feature.jdk7; - -import java.io.IOException; - -/** - * 多异常捕获 - */ -public class TryCatchMany { - - public static void main(String[] args) { - try (TxtRead txtRead = new TxtRead()) { - txtRead.reader(); - } catch (IOException | NoSuchFieldException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - } -} - -class TxtRead implements AutoCloseable { - - @Override - public void close() throws Exception { - System.out.println("资源释放"); - } - - public void reader() throws IOException, NoSuchFieldException { - System.out.println("数据读取"); - } -} -``` - -需要注意的是,一个 catch 捕获多个异常时,不能出现重复的异常类型,也不能出现一个异常类型是另一个类的子类的情况。 - -# 4. 二进制 - -Java 7 开始,可以直接指定不同的进制数字。 - -1. 二进制指定数字值,只需要使用 `0b` 或者 `OB` 开头。 -2. 八进制指定数字值,使用 `0` 开头。 -3. 十六进制指定数字值,使用 `0x` 开头。 - -```java -/** - * 二进制 - * - * @author www.wdbyte.com - */ -public class Binary { - public static void main(String[] args) { - // 二进制 - System.out.println("------2进制-----"); - int a = 0b001; - int b = 0b010; - System.out.println(a); - System.out.println(b); - // 八进制 - System.out.println("------8进制-----"); - int a1 = 010; - int b1 = 020; - System.out.println(a1); - System.out.println(b1); - // 十六进制 - System.out.println("------16进制-----"); - int a2 = 0x10; - int b2 = 0x20; - System.out.println(a2); - System.out.println(b2); - } -} -``` - -输出结果。 - -```log -------2进制----- -1 -2 -------8进制----- -8 -16 -------16进制----- -16 -32 -``` - -# 5. 数字下划线 - -Java 7 开始支持在数字定义时候使用下划线分割,增加了数字的可读性。 - -```java -/** - * 数字下环线 - * - * @author www.wdbyte.com - */ -public class NumberLine { - public static void main(String[] args) { - int a = 1_000; - int b = 1_0__0_0_0_____00; - System.out.println(a); - System.out.println(b); - } -} -``` - -得到结果。 - -```log -1000 -1000000 -``` - -# 6. 结束语 - -虽然 Java 7 早在 2011 年就已经发布了,但是据我发现,使用到 Java 7 开始的新特性新语法的并不多,所以我的 JDK 新特性系列文章计划从 Java 7 开始,一直介绍到目前已经发布的 Java 13,以后 Java 新版本更新的同时,这个新特性系列文章也会持续更新。 - -此去山高水远,愿能一路坚持,愿你我一路同行。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-08-feature-lambda.md b/docs/jdk/java-08-feature-lambda.md deleted file mode 100644 index 78325c3..0000000 --- a/docs/jdk/java-08-feature-lambda.md +++ /dev/null @@ -1,426 +0,0 @@ ---- -title: 还看不懂同事的代码?Lambda 表达式、函数接口了解一下 -# toc: false -date: 2019-11-11 08:01:01 -url: jdk/jdk8-lambda -tags: - - Java8 - - Lambda -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -当前时间:2019年 11月 11日,距离 JDK 14 发布时间(2020年3月17日)还有多少天? - -```java -// 距离JDK 14 发布还有多少天? -LocalDate jdk14 = LocalDate.of(2020, 3, 17); -LocalDate nowDate = LocalDate.now(); -System.out.println("距离JDK 14 发布还有:"+nowDate.until(jdk14,ChronoUnit.DAYS)+"天"); -``` - - - -# 1. 前言 - -`Java 8` 早已经在2014 年 3月 18日发布,毫无疑问 `Java 8` 对 Java 来说绝对算得上是一次重大版本更新,它包含了十多项语言、库、工具、JVM 等方面的十多项新特性。比如提供了语言级的匿名函数,也就是被官方称为 `Lambda` 的表达式语法(外界也称为闭包,` Lambda` 的引入也让流式操作成为可能,减少了代码编写的复杂性),比如函数式接口,方法引用,重复注解。再比如 `Optional` 预防空指针,`Stearm` 流式操作,`LocalDateTime` 时间操作等。 - -在前面的文章里已经介绍了 `Java 8` 的部分新特性。 - -1. [Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下?](https://www.wdbyte.com/2019/10/jdk/jdk8-time/) - -2. [Jdk14都要出了,还不能使用 Optional优雅的处理空指针?](https://www.wdbyte.com/2019/11/jdk/jdk8-optional/) - -这一次主要介绍一下 Lambda 的相关情况。 - -# 2. Lambda 介绍 - -`Lambda` 名字来源于希腊字母表中排序第十一位的字母 λ,大写为Λ,英语名称为 `Lambda`。在 Java 中 `Lambda` 表达式(lambda expression)是一个匿名函数,在编写 Java 中的 `Lambda` 的时候,你也会发现 `Lambda` 不仅没有函数名称,有时候甚至连入参和返回都可以省略,这也让代码变得更加紧凑。 - -# 3. 函数接口介绍 - -上面说了这次是介绍 `Lambda` 表达式,为什么要介绍函数接口呢?其实 Java 中的函数接口在使用时,可以隐式的转换成 `Lambda` 表达式,在 `Java 8`中已经有很多接口已经声明为函数接口,如 Runnable、Callable、Comparator 等。 - -函数接口的例子可以看下 `Java 8` 中的 `Runnable` 源码(去掉了注释)。 - -```java -package java.lang; - -@FunctionalInterface -public interface Runnable { - public abstract void run(); -} -``` - -那么什么样子的接口才是函数接口呢?有一个很简单的定义,也就是只有`一个抽象函数`的接口,函数接口使用注解 `@FunctionalInterface ` 进行声明(注解声明不是必须的,如果没有注解,也是只有一个抽象函数,依旧会被认为是函数接口)。多一个或者少一个抽象函数都不能定义为函数接口,如果使用了函数接口注解又不止一个抽象函数,那么编译器会拒绝编译。函数接口在使用时候可以隐式的转换成 Lambda 表达式。 - -`Java 8` 中很多有很多不同功能的函数接口定义,都放在了 `Java 8` 新增的 `java.util.function`包内。下面是一些关于 `Java 8` 中函数接口功能的描述。 - -| 序号 | 接口 & 描述 | -| :----------------------- | ------------------------------------------------------------ | -| **BiConsumer** | 代表了一个接受两个输入参数的操作,并且不返回任何结果 | -| **BiFunction** | 代表了一个接受两个输入参数的方法,并且返回一个结果 | -| **BinaryOperator** | 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 | -| **BiPredicate** | 代表了一个两个参数的boolean值方法 | -| **BooleanSupplier** | 代表了boolean值结果的提供方 | -| **Consumer** | 代表了接受一个输入参数并且无返回的操作 | -| **DoubleBinaryOperator** | 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 | -| **DoubleConsumer** | 代表一个接受double值参数的操作,并且不返回结果。 | -| **DoubleFunction** | 代表接受一个double值参数的方法,并且返回结果 | -| **DoublePredicate** | 代表一个拥有double值参数的boolean值方法 | -| **DoubleSupplier** | 代表一个double值结构的提供方 | -| **DoubleToIntFunction** | 接受一个double类型输入,返回一个int类型结果。 | -| **DoubleToLongFunction** | 接受一个double类型输入,返回一个long类型结果 | -| **DoubleUnaryOperator** | 接受一个参数同为类型double,返回值类型也为double 。 | -| **Function** | 接受一个输入参数,返回一个结果。 | -| **IntBinaryOperator** | 接受两个参数同为类型int,返回值类型也为int 。 | -| **IntConsumer** | 接受一个int类型的输入参数,无返回值 。 | -| **IntFunction** | 接受一个int类型输入参数,返回一个结果 。 | -| **IntPredicate** | 接受一个int输入参数,返回一个布尔值的结果。 | -| **IntSupplier** | 无参数,返回一个int类型结果。 | -| **IntToDoubleFunction** | 接受一个int类型输入,返回一个double类型结果 。 | -| **IntToLongFunction** | 接受一个int类型输入,返回一个long类型结果。 | -| **IntUnaryOperator** | 接受一个参数同为类型int,返回值类型也为int 。 | -| **LongBinaryOperator** | 接受两个参数同为类型long,返回值类型也为long。 | -| **LongConsumer** | 接受一个long类型的输入参数,无返回值。 | -| **LongFunction** | 接受一个long类型输入参数,返回一个结果。 | -| **LongPredicate** | 接受一个long输入参数,返回一个布尔值类型结果。 | -| **LongSupplier** | 无参数,返回一个结果long类型的值。 | -| **LongToDoubleFunction** | 接受一个long类型输入,返回一个double类型结果。 | -| **LongToIntFunction** | 接受一个long类型输入,返回一个int类型结果。 | -| **LongUnaryOperator** | 接受一个参数同为类型long,返回值类型也为long。 | -| **ObjDoubleConsumer** | 接受一个object类型和一个double类型的输入参数,无返回值。 | -| **ObjIntConsumer** | 接受一个object类型和一个int类型的输入参数,无返回值。 | -| **ObjLongConsumer** | 接受一个object类型和一个long类型的输入参数,无返回值。 | -| **Predicate** | 接受一个输入参数,返回一个布尔值结果。 | -| **Supplier** | 无参数,返回一个结果。 | -| **ToDoubleBiFunction** | 接受两个输入参数,返回一个double类型结果 | -| **ToDoubleFunction** | 接受一个输入参数,返回一个double类型结果 | -| **ToIntBiFunction** | 接受两个输入参数,返回一个int类型结果。 | -| **ToIntFunction** | 接受一个输入参数,返回一个int类型结果。 | -| **ToLongBiFunction** | 接受两个输入参数,返回一个long类型结果。 | -| **ToLongFunction** | 接受一个输入参数,返回一个long类型结果。 | -| **UnaryOperator** | 接受一个参数为类型T,返回值类型也为T。 | - -(上面表格来源于菜鸟教程) - -# 3. Lambda 语法 - -Lambda 的语法主要是下面几种。 - -1. (params) -> expression - -3. (params) -> {statements;} - -Lambda 的语法特性。 - 1. 使用 `->` 分割 Lambda 参数和处理语句。 - 2. 类型可选,可以不指定参数类型,编译器可以自动判断。 - 3. 圆括号可选,如果只有一个参数,可以不需要圆括号,多个参数必须要圆括号。 - 4. 花括号可选,一个语句可以不用花括号,多个参数则花括号必须。 - 5. 返回值可选,如果只有一个表达式,可以自动返回,不需要 return 语句;花括号中需要 return 语法。 - 6. Lambda 中引用的外部变量必须为 final 类型,内部声明的变量不可修改,内部声明的变量名称不能与外部变量名相同。 - -举几个具体的例子, params 在只有一个参数或者没有参数的时候,可以直接省略不写,像这样。 - -```java -// 1.不需要参数,没有返回值,输出 hello -()->System.out.pritnln("hello"); - -// 2.不需要参数,返回 hello -()->"hello"; - -// 3. 接受2个参数(数字),返回两数之和 -(x, y) -> x + y - -// 4. 接受2个数字参数,返回两数之和 -(int x, int y) -> x + y - -// 5. 两个数字参数,如果都大于10,返回和,如果都小于10,返回差 -(int x,int y) ->{ - if( x > 10 && y > 10){ - return x + y; - } - if( x < 10 && y < 10){ - return Math.abs(x-y); - } -}; -``` - -通过上面的几种情况,已经可以大致了解 Lambda 的语法结构了。 - -# 4. Lambda 使用 - -## 4.1 对于函数接口 - -从上面的介绍中已经知道了 Runnable 接口已经是函数接口了,它可以隐式的转换为 Lambda 表达式进行使用,通过下面的创建线程并运行的例子看下 `Java 8` 中 Lambda 表达式的具体使用方式。 - -```java -/** - * Lambda 的使用,使用 Runnable 例子 - * @throws InterruptedException - */ -@Test -public void createLambda() throws InterruptedException { - // 使用 Lambda 之前 - Runnable runnable = new Runnable() { - @Override - public void run() { - System.out.println("JDK8 之前的线程创建"); - } - }; - new Thread(runnable).start(); - // 使用 Lambda 之后 - Runnable runnable1Jdk8 = () -> System.out.println("JDK8 之后的线程创建"); - new Thread(runnable1Jdk8).start(); - // 更加紧凑的方式 - new Thread(() -> System.out.println("JDK8 之后的线程创建")).start(); -} -``` - -可以发现 `Java 8` 中的 `Lambda` 碰到了函数接口 Runnable,自动推断了要运行的 run 方法,不仅省去了 run 方法的编写,也代码变得更加紧凑。 - -运行得到结果如下。 - -```shell -JDK8 之前的线程创建 -JDK8 之后的线程创建 -JDK8 之后的线程创建 -``` - -上面的 Runnable 函数接口里的 run 方法是没有参数的情况,如果是有参数的,那么怎么使用呢?我们编写一个函数接口,写一个 `say` 方法接受两个参数。 - -```java -/** - * 定义函数接口 - */ -@FunctionalInterface -public interface FunctionInterfaceDemo { - void say(String name, int age); -}  -``` - -编写一个测试类。 - -```java - /** - * 函数接口,Lambda 测试 - */ - @Test - public void functionLambdaTest() { - FunctionInterfaceDemo demo = (name, age) -> System.out.println("我叫" + name + ",我今年" + age + "岁了"); - demo.say("金庸", 99); - } -``` - -输出结果。 - -```java -我叫金庸,我今年99岁了。 -``` - -## 4.2 对于方法引用 - -方法引用这个概念前面还没有介绍过,方法引用可以让我们直接访问类的实例或者方法,在 Lambda 只是执行一个方法的时候,就可以不用 `Lambda` 的编写方式,而用方法引用的方式:`实例/类::方法`。这样不仅代码更加的紧凑,而且可以增加代码的可读性。 - -通过一个例子查看方法引用。 - -```java -@Getter -@Setter -@ToString -@AllArgsConstructor -static class User { - private String name; - private Integer age; -} -public static List userList = new ArrayList(); -static { - userList.add(new User("A", 26)); - userList.add(new User("B", 18)); - userList.add(new User("C", 23)); - userList.add(new User("D", 19)); -} -/** - * 测试方法引用 - */ -@Test -public void methodRef() { - User[] userArr = new User[userList.size()]; - userList.toArray(userArr); - // User::getAge 调用 getAge 方法 - Arrays.sort(userArr, Comparator.comparing(User::getAge)); - for (User user : userArr) { - System.out.println(user); - } -} -``` - -得到输出结果。 - -Jdk8Lambda.User(name=B, age=18) -Jdk8Lambda.User(name=D, age=19) -Jdk8Lambda.User(name=C, age=23) -Jdk8Lambda.User(name=A, age=26) - -## 4.3 对于遍历方式 - -Lambda 带来了新的遍历方式,`Java 8` 为集合增加了 `foreach` 方法,它可以接受函数接口进行操作。下面看一下 `Lambda` 的集合遍历方式。 - -```java -/** - * 新的遍历方式 - */ -@Test -public void foreachTest() { - List skills = Arrays.asList("java", "golang", "c++", "c", "python"); - // 使用 Lambda 之前 - for (String skill : skills) { - System.out.print(skill+","); - } - System.out.println(); - // 使用 Lambda 之后 - // 方式1,forEach+lambda - skills.forEach((skill) -> System.out.print(skill+",")); - System.out.println(); - // 方式2,forEach+方法引用 - skills.forEach(System.out::print); -} -``` - -运行得到输出。 - -```shell -java,golang,c++,c,python, -java,golang,c++,c,python, -javagolangc++cpython -``` - -## 4.4 对于流式操作 - -得益于 `Lambda` 的引入,让 `Java 8` 中的流式操作成为可能,`Java 8` 提供了 stream 类用于获取数据流,它专注对数据集合进行各种高效便利操作,提高了编程效率,且同时支持串行和并行的两种模式汇聚计算。能充分的利用多核优势。 - -流式操作如此强大, `Lambda` 在流式操作中怎么使用呢?下面来感受流操作带来的方便与高效。 - -流式操作一切从这里开始。 - -```java -// 为集合创建串行流 -stream() -// 为集合创建并行流 -parallelStream() -``` - -流式操作的去重 `distinct`和过滤 `filter`。 - -```java -@Test -public void streamTest() { - List skills = Arrays.asList("java", "golang", "c++", "c", "python", "java"); - // Jdk8 之前 - for (String skill : skills) { - System.out.print(skill + ","); - } - System.out.println(); - // Jdk8 之后-去重遍历 - skills.stream().distinct().forEach(skill -> System.out.print(skill + ",")); - System.out.println(); - // Jdk8 之后-去重遍历 - skills.stream().distinct().forEach(System.out::print); - System.out.println(); - // Jdk8 之后-去重,过滤掉 ptyhon 再遍历 - skills.stream().distinct().filter(skill -> skill != "python").forEach(skill -> System.out.print(skill + ",")); - System.out.println(); - // Jdk8 之后转字符串 - String skillString = String.join(",", skills); - System.out.println(skillString); -} -``` - -运行得到结果。 - -```shell -java,golang,c++,c,python,java, -java,golang,c++,c,python, -javagolangc++cpython -java,golang,c++,c, -java,golang,c++,c,python,java -``` - -流式操作的数据转换(也称映射)`map`。 - -```java - /** - * 数据转换 - */ - @Test - public void mapTest() { - List numList = Arrays.asList(1, 2, 3, 4, 5); - // 数据转换 - numList.stream().map(num -> num * num).forEach(num -> System.out.print(num + ",")); - - System.out.println(); - - // 数据收集 - Set numSet = numList.stream().map(num -> num * num).collect(Collectors.toSet()); - numSet.forEach(num -> System.out.print(num + ",")); - } -``` - -运行得到结果。 - -```shell -1,4,9,16,25, -16,1,4,9,25, -``` - -流式操作的数学计算。 - -```java -/** - * 数学计算测试 - */ -@Test -public void mapMathTest() { - List list = Arrays.asList(1, 2, 3, 4, 5); - IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics(); - System.out.println("最小值:" + stats.getMin()); - System.out.println("最大值:" + stats.getMax()); - System.out.println("个数:" + stats.getCount()); - System.out.println("和:" + stats.getSum()); - System.out.println("平均数:" + stats.getAverage()); - // 求和的另一种方式 - Integer integer = list.stream().reduce((sum, cost) -> sum + cost).get(); - System.out.println(integer); -} -``` - -运行得到结果。 - -```shell -得到输出 -最小值:1 -最大值:5 -个数:5 -和:15 -平均数:3.0 -15 -``` - -# 5. Lambda 总结 - -`Lamdba` 结合函数接口,方法引用,类型推导以及流式操作,可以让代码变得更加简洁紧凑,也可以借此开发出更加强大且支持并行计算的程序,函数编程也为 Java 带来了新的程序设计方式。但是缺点也很明显,在实际的使用过程中可能会发现调式困难,测试表示 `Lamdba` 的遍历性能并不如 for 的性能高,同事可能没有学习导致看不懂 `Lamdba` 等(可以推荐来看这篇文章)。 - -文章代码已经上传到 [https://github.com/niumoo/jdk-feature)](https://github.com/niumoo/jdk-feature) 。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-08-feature-optional.md b/docs/jdk/java-08-feature-optional.md deleted file mode 100644 index 5f6005f..0000000 --- a/docs/jdk/java-08-feature-optional.md +++ /dev/null @@ -1,357 +0,0 @@ ---- -title: Jdk14都要出了,还不能使用 Optional优雅的处理空指针? -# toc: false -date: 2019-11-04 08:01:01 -url: jdk/jdk8-optional -tags: - - Java8 - - Optional -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -# 1. 前言 - -> 如果你没有处理过空指针,那么你不是一位真正的 Java 程序员。 - -空指针确实会产生很多问题,我们经常遇到空的引用,然后又想从这个空的引用上去获取其他的值,接着理所当然的碰到了 `NullPointException`。这是你可能会想,这报错很好处理,然后你看了眼报错行数,对比了下代码。脑海里瞬间闪过 ”对对对,这里有可能为空“,然后加上 `null check`轻松处理。然而你不知道这已经是你处理的第多少个空指针异常了。 - -为了解决上面的问题,在 Java SE8 中引入了一个新类 `java.util.Optional`,这个类可以**缓解**上面的问题。 - -你可能已经发现了,上面我用的是**缓解**而不是**解决**。这也是很多人理解不太对的地方,以为 Java SE8 中的 `Optional` 类可以解决空指针问题。其实 Optional 类的的使用只是**提示**你这里可能存在空值,需要特殊处理,并提供了一些特殊处理的方法。如果你把 `Optional` 类当作空指针的救命稻草而不加思考的使用,那么依旧会碰到错误。 - -因为 `Optional` 是的 Java SE8 中引入的,因此本文中难免会有一些 JDK8 中的语法,如 **Lambda** 表达式,流处理等,但是都是基本形式,不会有过于复杂的案例。 - -# 2. Optional 创建 - -Optional 的创建一共有三种方式。 - -```java -/** - * 创建一个 Optional - */ -@Test -public void createOptionalTest() { - // Optional 构造方式1 - of 传入的值不能为 null - Optional helloOption = Optional.of("hello"); - - // Optional 构造方式2 - empty 一个空 optional - Optional emptyOptional = Optional.empty(); - - // Optional 构造方式3 - ofNullable 支持传入 null 值的 optional - Optional nullOptional = Optional.ofNullable(null); -} -``` - -其中构造方式1中 `of` 方法,如果传入的值会空,会报出 `NullPointerException` 异常。 - -# 3. Optional 判断 - -Optional 只是一个包装对象,想要判断里面有没有值可以使用 `isPresent` 方法检查其中是否有值 。 - -```java -/** - * 检查是否有值 - */ -@Test -public void checkOptionalTest() { - Optional helloOptional = Optional.of("Hello"); - System.out.println(helloOptional.isPresent()); - - Optional emptyOptional = Optional.empty(); - System.out.println(emptyOptional.isPresent()); -} -``` - -得到的输出: - -```java -true -false -``` - -从 JDK11 开始,提供了 `isEmpty`方法用来检查相反的结果:是否为空。 - -如果想要在有值的时候进行一下操作。可以使用 `ifPresent`方法。 - -```java -/** - * 如果有值,输出长度 - */ -@Test -public void whenIsPresent() { - // 如果没有值,获取默认值 - Optional helloOptional = Optional.of("Hello"); - Optional emptyOptional = Optional.empty(); - helloOptional.ifPresent(s -> System.out.println(s.length())); - emptyOptional.ifPresent(s -> System.out.println(s.length())); -} -``` - -输出结果: - -```java -5 -``` - -# 4. Optional 获取值 - -使用 `get`方法可以获取值,但是如果值不存在,会抛出 `NoSuchElementException` 异常。 - -```java -/** - * 如果没有值,会抛异常 - */ -@Test -public void getTest() { - Optional stringOptional = Optional.of("hello"); - System.out.println(stringOptional.get()); - // 如果没有值,会抛异常 - Optional emptyOptional = Optional.empty(); - System.out.println(emptyOptional.get()); -} -``` - -得到结果: - -```java -hello - -java.util.NoSuchElementException: No value present - at java.util.Optional.get(Optional.java:135) - at net.codingme.feature.jdk8.Jdk8Optional.getTest(Jdk8Optional.java:91) -``` - - - -# 5. Optional 默认值 - -使用 `orElse`, `orElseGet` 方法可以在没有值的情况下获取给定的默认值。 - -```java -/** - * 如果没有值,获取默认值 - */ -@Test -public void whenIsNullGetTest() { - // 如果没有值,获取默认值 - Optional emptyOptional = Optional.empty(); - String orElse = emptyOptional.orElse("orElse default"); - String orElseGet = emptyOptional.orElseGet(() -> "orElseGet default"); - System.out.println(orElse); - System.out.println(orElseGet); -} -``` -得到的结果: -```log -orElse default -orElseGet default -``` -看到这里你可能会有些疑惑了,这两个方法看起来效果是一模一样的,为什么会提供两个呢?下面再看一个例子,你会发现两者的区别。 -```java - /** - * orElse 和 orElseGet 的区别 - */ -@Test -public void orElseAndOrElseGetTest() { - // 如果没有值,默认值 - Optional emptyOptional = Optional.empty(); - System.out.println("空Optional.orElse"); - String orElse = emptyOptional.orElse(getDefault()); - System.out.println("空Optional.orElseGet"); - String orElseGet = emptyOptional.orElseGet(() -> getDefault()); - System.out.println("空Optional.orElse结果:"+orElse); - System.out.println("空Optional.orElseGet结果:"+orElseGet); - System.out.println("--------------------------------"); - // 如果没有值,默认值 - Optional stringOptional = Optional.of("hello"); - System.out.println("有值Optional.orElse"); - orElse = stringOptional.orElse(getDefault()); - System.out.println("有值Optional.orElseGet"); - orElseGet = stringOptional.orElseGet(() -> getDefault()); - System.out.println("有值Optional.orElse结果:"+orElse); - System.out.println("有值Optional.orElseGet结果:"+orElseGet); -} - -public String getDefault() { - System.out.println(" 获取默认值中..run getDeafult method"); - return "hello"; -} -``` - -得到的输出: - -```log -空Optional.orElse - 获取默认值中..run getDeafult method -空Optional.orElseGet - 获取默认值中..run getDeafult method -空Optional.orElse结果:hello -空Optional.orElseGet结果:hello --------------------------------- -有值Optional.orElse - 获取默认值中..run getDeafult method -有值Optional.orElseGet -有值Optional.orElse结果:hello -有值Optional.orElseGet结果:hello -``` - -在这个例子中会发现 `orElseGet` 传入的方法在有值的情况下并不会运行。而 `orElse`却都会运行。 - -# 6. Optional 异常 - -使用 `orElseThrow` 在没有值的时候抛出异常 - -```java -/** - * 如果没有值,抛出异常 - */ -@Test -public void whenIsNullThrowExceTest() throws Exception { - // 如果没有值,抛出异常 - Optional emptyOptional = Optional.empty(); - String value = emptyOptional.orElseThrow(() -> new Exception("发现空值")); - System.out.println(value); -} -``` - -得到结果: - -```java -java.lang.Exception: 发现空值 - at net.codingme.feature.jdk8.Jdk8Optional.lambda$whenIsNullThrowExceTest$7(Jdk8Optional.java:118) - at java.util.Optional.orElseThrow(Optional.java:290) - at net.codingme.feature.jdk8.Jdk8Optional.whenIsNullThrowExceTest(Jdk8Optional.java:118) -``` - -# 7. Optional 函数接口 - -`Optional` 随 JDK8 一同出现,必然会有一些 JDK8 中的新特性,比如函数接口。`Optional` 中主要有三个传入函数接口的方法,分别是`filter`,`map`,`flatMap`。这里面的实现其实是 JDK8 的另一个新特性了,因此这里只是简单演示,不做解释。后面放到其他 JDK8 新特性文章里介绍。 - -```java -@Test -public void functionTest() { - // filter 过滤 - Optional optional123 = Optional.of(123); - optional123.filter(num -> num == 123).ifPresent(num -> System.out.println(num)); - - Optional optional456 = Optional.of(456); - optional456.filter(num -> num == 123).ifPresent(num -> System.out.println(num)); - - // map 转换 - Optional optional789 = Optional.of(789); - optional789.map(String::valueOf).map(String::length).ifPresent(length -> System.out.println(length)); -} -``` - -得到结果: - -```java -123 -3 -``` - -# 8. Optional 案例 - -假设有计算机、声卡、usb 三种硬件(下面的代码中使用了 `Lombok` 的 `@Data` 注解)。 - -```java -/** - * 计算机 - */ -@Data -class Computer { - private Optional soundCard; -} - -/** - * 声卡 - */ -@Data -class SoundCard { - private Optional usb; -} - -/** - * USB - */ -@Data -class Usb { - private String version; -} -``` - -计算机可能会有声卡,声卡可能会有 usb。那么怎么取得 usb 版本呢? - -```java -/** - * 电脑里【有可能】有声卡 - * 声卡【有可能】有USB接口 - */ -@Test -public void optionalTest() { - // 没有声卡,没有 Usb 的电脑 - Computer computerNoUsb = new Computer(); - computerNoUsb.setSoundCard(Optional.empty()); - // 获取 usb 版本 - Optional computerOptional = Optional.ofNullable(computerNoUsb); - String version = computerOptional.flatMap(Computer::getSoundCard).flatMap(SoundCard::getUsb) - .map(Usb::getVersion).orElse("UNKNOWN"); - System.out.println(version); - System.out.println("-----------------"); - - // 如果有值,则输出 - SoundCard soundCard = new SoundCard(); - Usb usb = new Usb(); - usb.setVersion("2.0"); - soundCard.setUsb(Optional.ofNullable(usb)); - Optional optionalSoundCard = Optional.ofNullable(soundCard); - optionalSoundCard.ifPresent(System.out::println); - // 如果有值,则输出 - if (optionalSoundCard.isPresent()) { - System.out.println(optionalSoundCard.get()); - } - - // 输出没有值,则没有输出 - Optional optionalSoundCardEmpty = Optional.ofNullable(null); - optionalSoundCardEmpty.ifPresent(System.out::println); - System.out.println("-----------------"); - - // 筛选 Usb2.0 - optionalSoundCard.map(SoundCard::getUsb) - .filter(usb1 -> "3.0".equals(usb1.map(Usb::getVersion) - .orElse("UBKNOW"))) - .ifPresent(System.out::println); -} -``` - -得到结果: - -```java - -UNKNOWN ------------------ -SoundCard(usb=Optional[Usb(version=2.0)]) -SoundCard(usb=Optional[Usb(version=2.0)]) ------------------ -``` - -# 9. Optional 总结 - -在本文中,我们看到了如何使用 Java SE8 的 `java.util.Optional` 类。`Optional` 类的目的不是为了替换代码中的每个空引用,而是为了帮助更好的设计程序,让使用者可以仅通过观察属性类型就可以知道会不会有空值。另外,`Optional`不提供直接获取值的方法,使用时会强迫你处理不存在的情况。间接的让你的程序免受空指针的影响。 - - - -文中代码已经上传 [Github](https://github.com/niumoo/jdk-feature)。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-08-feature-stream.md b/docs/jdk/java-08-feature-stream.md deleted file mode 100644 index 7c0656e..0000000 --- a/docs/jdk/java-08-feature-stream.md +++ /dev/null @@ -1,601 +0,0 @@ ---- -title: 还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下 -# toc: false -date: 2019-11-18 09:00:00 -url: jdk/jdk8-stream -tags: - - Stream - - Java8 -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![java-streams](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/java-streams.png) - -Java 8 新特性系列文章索引。 - -1. [Jdk14都要出了,还不能使用 Optional优雅的处理空指针?](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483879&idx=1&sn=1eb37f5a97fda31ebb9d80d6e96cfb88&chksm=e984e883def361957df3a954b0f28775404b5f0a278958e91c65b3f3175a37f5384b0988c564&scene=21#wechat_redirect) -2. [Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下?](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483801&idx=1&sn=eea69b039feb1ae86187ade222e6bfd8&chksm=e984e8fddef361ebd4acc58e11f3ccdeea9b06b6514957a5c203d52046c551f3ea3203d187e9&scene=21#wechat_redirect) -3. [还看不懂同事的代码?Lambda 表达式、函数接口了解一下](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483923&idx=1&sn=57c720a9ba7dbd79e84a069e0d6fa84f&chksm=e984eb77def36261de6f0b9edd8aaa9ef34f74f90c08e6d395880545b6bf1a7b054ecdbff483#rd) - -## 前言 - -我们都知道 `Lambda` 和 Stream 是 Java 8 的两大亮点功能,在前面的文章里已经介绍过 `Lambda` 相关知识,这次介绍下 Java 8 的 Stream 流操作。它完全不同于 java.io 包的 Input/Output Stream ,也不是大数据实时处理的 Stream 流。这个 Stream 流操作是 Java 8 对集合操作功能的增强,专注于对集合的各种高效、便利、优雅的**聚合操作**。借助于 `Lambda` 表达式,显著的提高**编程效率**和**可读性**。且 Stream 提供了**并行计算**模式,可以简洁的编写出并行代码,能充分发挥如今计算机的多核处理优势。 - - - -在使用 Stream 流操作之前你应该先了解 `Lambda` 相关知识,如果还不了解,可以参考之前文章:[还看不懂同事的代码?Lambda 表达式、函数接口了解一下](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483923&idx=1&sn=57c720a9ba7dbd79e84a069e0d6fa84f&chksm=e984eb77def36261de6f0b9edd8aaa9ef34f74f90c08e6d395880545b6bf1a7b054ecdbff483#rd) 。 - -## 1. Stream 流介绍 - -Stream 不同于其他集合框架,它也不是某种数据结构,也不会保存数据,但是它负责相关计算,使用起来更像一个高级的迭代器。在之前的迭代器中,我们只能先遍历然后在执行业务操作,而现在只需要指定执行什么操作, Stream 就会隐式的遍历然后做出想要的操作。另外 Stream 和迭代器一样的只能单向处理,如同奔腾长江之水一去而不复返。 - -由于 Stream 流提供了**惰性计算**和**并行处理**的能力,在使用并行计算方式时数据会被自动分解成多段然后并行处理,最后将结果汇总。所以 Stream 操作可以让程序运行变得更加高效。 - -## 2. Stream 流概念 - -Stream 流的使用总是按照一定的步骤进行,可以抽象出下面的使用流程。 - -> 数据源(source) -> 数据处理/转换(intermedia) -> 结果处理(terminal ) - -### 2.1. 数据源 - -`数据源(source)`也就是数据的来源,可以通过多种方式获得 Stream 数据源,下面列举几种常见的获取方式。 - -- Collection.stream(); 从集合获取流。 -- Collection.parallelStream(); 从集合获取**并行流。** -- Arrays.stream(T array) or Stream.of(); 从数组获取流。 -- BufferedReader.lines(); 从输入流中获取流。 -- IntStream.of() ; 从静态方法中获取流。 -- Stream.generate(); 自己生成流 - -### 2.2. 数据处理 - -`数据处理/转换(intermedia)`步骤可以有多个操作,这步也被称为`intermedia`(中间操作)。在这个步骤中不管怎样操作,它返回的都是一个新的流对象,原始数据不会发生任何改变,而且这个步骤是`惰性计算`处理的,也就是说只调用方法并不会开始处理,只有在真正的开始收集结果时,中间操作才会生效,而且如果遍历没有完成,想要的结果已经获取到了(比如获取第一个值),会停止遍历,然后返回结果。`惰性计算`可以显著提高运行效率。 - -数据处理演示。 - -```java -@Test -public void streamDemo(){ - List nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"); - // 1. 筛选出名字长度为4的 - // 2. 名字前面拼接 This is - // 3. 遍历输出 - nameList.stream() - .filter(name -> name.length() == 4) - .map(name -> "This is "+name) - .forEach(name -> System.out.println(name)); -} -// 输出结果 -// This is Jack -// This is Poul -``` - -`数据处理/转换`操作自然不止是上面演示的过滤 `filter` 和 `map`映射两种,另外还有 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等。 - -### 2.3. 收集结果 - -`结果处理(terminal )`是流处理的最后一步,执行完这一步之后流会被彻底用尽,流也不能继续操作了。也只有到了这个操作的时候,流的`数据处理/转换`等中间过程才会开始计算,也就是上面所说的`惰性计算`。`结果处理`也必定是流操作的最后一步。 - -常见的``结果处理``操作有 forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator 等。 - -下面演示了简单的``结果处理``的例子。 - -```java -/** - * 转换成为大写然后收集结果,遍历输出 - */ -@Test -public void toUpperCaseDemo() { - List nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"); - List upperCaseNameList = nameList.stream() - .map(String::toUpperCase) - .collect(Collectors.toList()); - upperCaseNameList.forEach(name -> System.out.println(name + ",")); -} -// 输出结果 -// DARCY,CHRIS,LINDA,SID,KIM,JACK,POUL,PETER, -``` - -### 2.4. short-circuiting - -有一种 Stream 操作被称作 `short-circuiting` ,它是指当 Stream 流**无限大**但是需要返回的 Stream 流是**有限**的时候,而又希望它能在**有限的时间**内计算出结果,那么这个操作就被称为`short-circuiting`。例如 `findFirst `操作。 - -## 3. Stream 流使用 - -Stream 流在使用时候总是借助于 `Lambda` 表达式进行操作,Stream 流的操作也有很多种方式,下面列举的是常用的 11 种操作。 - -### 3.1. Stream 流获取 - -获取 Stream 的几种方式在上面的 Stream 数据源里已经介绍过了,下面是针对上面介绍的几种获取 Stream 流的使用示例。 - -```java -@Test -public void createStream() throws FileNotFoundException { - List nameList = Arrays.asList("Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"); - String[] nameArr = {"Darcy", "Chris", "Linda", "Sid", "Kim", "Jack", "Poul", "Peter"}; - // 集合获取 Stream 流 - Stream nameListStream = nameList.stream(); - // 集合获取并行 Stream 流 - Stream nameListStream2 = nameList.parallelStream(); - // 数组获取 Stream 流 - Stream nameArrStream = Stream.of(nameArr); - // 数组获取 Stream 流 - Stream nameArrStream1 = Arrays.stream(nameArr); - // 文件流获取 Stream 流 - BufferedReader bufferedReader = new BufferedReader(new FileReader("README.md")); - Stream linesStream = bufferedReader.lines(); - // 从静态方法获取流操作 - IntStream rangeStream = IntStream.range(1, 10); - rangeStream.limit(10).forEach(num -> System.out.print(num+",")); - System.out.println(); - IntStream intStream = IntStream.of(1, 2, 3, 3, 4); - intStream.forEach(num -> System.out.print(num+",")); -} -``` - -### 3.2. forEach - -``forEach`` 是 `Stream` 流中的一个重要方法,用于遍历 `Stream` 流,它支持传入一个标准的 `Lambda` 表达式。但是它的遍历不能通过 return/break 进行终止。同时它也是一个 `terminal` 操作,执行之后 `Stream` 流中的数据会被消费掉。 - -如输出对象。 - -```java -List numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); -numberList.stream().forEach(number -> System.out.println(number+",")); -// 输出结果 -// 1,2,3,4,5,6,7,8,9, -``` - -### 3.3. map / flatMap - -使用 `map` 把对象一对一映射成另一种对象或者形式。 - -```java -/** - * 把数字值乘以2 - */ -@Test -public void mapTest() { - List numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); - // 映射成 2倍数字 - List collect = numberList.stream() - .map(number -> number * 2) - .collect(Collectors.toList()); - collect.forEach(number -> System.out.print(number + ",")); - System.out.println(); - - numberList.stream() - .map(number -> "数字 " + number + ",") - .forEach(number -> System.out.println(number)); -} -// 输出结果 -// 2,4,6,8,10,12,14,16,18, -// 数字 1,数字 2,数字 3,数字 4,数字 5,数字 6,数字 7,数字 8,数字 9, -``` - -上面的 `map` 可以把数据进行一对一的映射,而有些时候关系可能不止 1对 1那么简单,可能会有1对多。这时可以使用 `flatMap。下面演示`使用 `flatMap`把对象扁平化展开。 - -```java -/** - * flatmap把对象扁平化 - */ -@Test -public void flatMapTest() { - Stream> inputStream = Stream.of( - Arrays.asList(1), - Arrays.asList(2, 3), - Arrays.asList(4, 5, 6) - ); - List collect = inputStream - .flatMap((childList) -> childList.stream()) - .collect(Collectors.toList()); - collect.forEach(number -> System.out.print(number + ",")); -} -// 输出结果 -// 1,2,3,4,5,6, -``` - -### 3.4. filter - -使用 `filter` 进行数据筛选,挑选出想要的元素,下面的例子演示怎么挑选出偶数数字。 - -```java -/** - * filter 数据筛选 - * 筛选出偶数数字 - */ -@Test -public void filterTest() { - List numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); - List collect = numberList.stream() - .filter(number -> number % 2 == 0) - .collect(Collectors.toList()); - collect.forEach(number -> System.out.print(number + ",")); -} -``` - -得到如下结果。 - -```shell -2,4,6,8, -``` - -### 3.5. findFirst - -`findFirst` 可以查找出 `Stream` 流中的第一个元素,它返回的是一个 Optional 类型,如果还不知道 Optional 类的用处,可以参考之前文章 [Jdk14都要出了,还不能使用 Optional优雅的处理空指针?](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483879&idx=1&sn=1eb37f5a97fda31ebb9d80d6e96cfb88&chksm=e984e883def361957df3a954b0f28775404b5f0a278958e91c65b3f3175a37f5384b0988c564&scene=21#wechat_redirect) 。 - -```java -/** - * 查找第一个数据 - * 返回的是一个 Optional 对象 - */ -@Test -public void findFirstTest(){ - List numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); - Optional firstNumber = numberList.stream() - .findFirst(); - System.out.println(firstNumber.orElse(-1)); -} -// 输出结果 -// 1 -``` - -`findFirst` 方法在查找到需要的数据之后就会返回**不再遍历**数据了,也因此 `findFirst` 方法可以对有无限数据的 `Stream` 流进行操作,也可以说 `findFirst` 是一个 `short-circuiting` 操作。 - -### 3.6. collect / toArray - -`Stream` 流可以轻松的转换为其他结构,下面是几种常见的示例。 - -```java - /** - * Stream 转换为其他数据结构 - */ -@Test -public void collectTest() { - List numberList = Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5); - // to array - Integer[] toArray = numberList.stream() - .toArray(Integer[]::new); - // to List - List integerList = numberList.stream() - .collect(Collectors.toList()); - // to set - Set integerSet = numberList.stream() - .collect(Collectors.toSet()); - System.out.println(integerSet); - // to string - String toString = numberList.stream() - .map(number -> String.valueOf(number)) - .collect(Collectors.joining()).toString(); - System.out.println(toString); - // to string split by , - String toStringbJoin = numberList.stream() - .map(number -> String.valueOf(number)) - .collect(Collectors.joining(",")).toString(); - System.out.println(toStringbJoin); -} -// 输出结果 -// [1, 2, 3, 4, 5] -// 112233445 -// 1,1,2,2,3,3,4,4,5 -``` - -### 3.7. limit / skip - -获取或者扔掉前 n 个元素 - -```java -/** - * 获取 / 扔掉前 n 个元素 - */ -@Test -public void limitOrSkipTest() { - // 生成自己的随机数流 - List ageList = Arrays.asList(11, 22, 13, 14, 25, 26); - ageList.stream() - .limit(3) - .forEach(age -> System.out.print(age+",")); - System.out.println(); - - ageList.stream() - .skip(3) - .forEach(age -> System.out.print(age+",")); -} -// 输出结果 -// 11,22,13, -// 14,25,26, -``` - -### 3.8. Statistics - -数学统计功能,求一组数组的最大值、最小值、个数、数据和、平均数等。 - -```java -/** - * 数学计算测试 - */ -@Test -public void mathTest() { - List list = Arrays.asList(1, 2, 3, 4, 5, 6); - IntSummaryStatistics stats = list.stream().mapToInt(x -> x).summaryStatistics(); - System.out.println("最小值:" + stats.getMin()); - System.out.println("最大值:" + stats.getMax()); - System.out.println("个数:" + stats.getCount()); - System.out.println("和:" + stats.getSum()); - System.out.println("平均数:" + stats.getAverage()); -} -// 输出结果 -// 最小值:1 -// 最大值:6 -// 个数:6 -// 和:21 -// 平均数:3.5 -``` - -### 3.9. groupingBy - -分组聚合功能,和数据库的 Group by 的功能一致。 - -```java -/** - * groupingBy - * 按年龄分组 - */ -@Test -public void groupByTest() { - List ageList = Arrays.asList(11, 22, 13, 14, 25, 26); - Map> ageGrouyByMap = ageList.stream() - .collect(Collectors.groupingBy(age -> String.valueOf(age / 10))); - ageGrouyByMap.forEach((k, v) -> { - System.out.println("年龄" + k + "0多岁的有:" + v); - }); -} -// 输出结果 -// 年龄10多岁的有:[11, 13, 14] -// 年龄20多岁的有:[22, 25, 26] -``` - -### 3.10. partitioningBy - -```java -/** - * partitioningBy - * 按某个条件分组 - * 给一组年龄,分出成年人和未成年人 - */ -public void partitioningByTest() { - List ageList = Arrays.asList(11, 22, 13, 14, 25, 26); - Map> ageMap = ageList.stream() - .collect(Collectors.partitioningBy(age -> age > 18)); - System.out.println("未成年人:" + ageMap.get(false)); - System.out.println("成年人:" + ageMap.get(true)); -} -// 输出结果 -// 未成年人:[11, 13, 14] -// 成年人:[22, 25, 26] -``` - -### 3.11. 进阶 - 自己生成 Stream 流 - -```java -/** - * 生成自己的 Stream 流 - */ -@Test -public void generateTest(){ - // 生成自己的随机数流 - Random random = new Random(); - Stream generateRandom = Stream.generate(random::nextInt); - generateRandom.limit(5).forEach(System.out::println); - // 生成自己的 UUID 流 - Stream generate = Stream.generate(UUID::randomUUID); - generate.limit(5).forEach(System.out::println); -} - -// 输出结果 -// 793776932 -// -2051545609 -// -917435897 -// 298077102 -// -1626306315 -// 31277974-841a-4ad0-a809-80ae105228bd -// f14918aa-2f94-4774-afcf-fba08250674c -// d86ccefe-1cd2-4eb4-bb0c-74858f2a7864 -// 4905724b-1df5-48f4-9948-fa9c64c7e1c9 -// 3af2a07f-0855-455f-a339-6e890e533ab3 -``` - -上面的例子中 `Stream` 流是无限的,但是获取到的结果是有限的,使用了 `Limit` 限制获取的数量,所以这个操作也是 `short-circuiting` 操作。 - -## 4. Stream 流优点 - -### 4.1. 简洁优雅 - -正确使用并且**正确格式化**的 `Stream` 流操作代码不仅**简洁优雅**,更让人赏心悦目。下面对比下在使用 `Stream` 流和不使用 `Stream` 流时相同操作的编码风格。 - -```java -/** - * 使用流操作和不使用流操作的编码风格对比 - */ -@Test -public void diffTest() { - // 不使用流操作 - List names = Arrays.asList("Jack", "Jill", "Nate", "Kara", "Kim", "Jullie", "Paul", "Peter"); - // 筛选出长度为4的名字 - List subList = new ArrayList<>(); - for (String name : names) { - if (name.length() == 4) { - subList.add(name); - } - } - // 把值用逗号分隔 - StringBuilder sbNames = new StringBuilder(); - for (int i = 0; i < subList.size() - 1; i++) { - sbNames.append(subList.get(i)); - sbNames.append(", "); - } - // 去掉最后一个逗号 - if (subList.size() > 1) { - sbNames.append(subList.get(subList.size() - 1)); - } - System.out.println(sbNames); -} -// 输出结果 -// Jack, Jill, Nate, Kara, Paul -``` - -如果是使用 `Stream` 流操作。 - -```java -// 使用 Stream 流操作 -String nameString = names.stream() - .filter(num -> num.length() == 4) - .collect(Collectors.joining(", ")); -System.out.println(nameString); -``` - -### 4.2. 惰性计算 - -上面有提到,`数据处理/转换(intermedia)` 操作 map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered 等这些操作,在调用方法时并不会立即调用,而是在真正使用的时候才会生效,这样可以让操作延迟到真正需要使用的时刻。 - -下面会举个例子演示这一点。 - -```java - /** - * 找出偶数 - */ - @Test - public void lazyTest() { - // 生成自己的随机数流 - List numberLIst = Arrays.asList(1, 2, 3, 4, 5, 6); - // 找出偶数 - Stream integerStream = numberLIst.stream() - .filter(number -> { - int temp = number % 2; - if (temp == 0 ){ - System.out.println(number); - } - return temp == 0; - }); - - System.out.println("分割线"); - List collect = integerStream.collect(Collectors.toList()); - } -``` - -如果没有 `惰性计算`,那么很明显会先输出偶数,然后输出 `分割线`。而实际的效果是。 - -```shell -分割线 -2 -4 -6 -``` - -可见 `惰性计算` 把计算延迟到了真正需要的时候。 - -### 4.3. 并行计算 - -获取 `Stream` 流时可以使用 `parallelStream` 方法代替 `stream` 方法以获取并行处理流,并行处理可以充分的发挥多核优势,而且不增加编码的复杂性。 - -下面的代码演示了生成一千万个随机数后,把每个随机数乘以2然后求和时,串行计算和并行计算的耗时差异。 - -```java - /** - * 并行计算 - */ - @Test - public void main() { - // 生成自己的随机数流,取一千万个随机数 - Random random = new Random(); - Stream generateRandom = Stream.generate(random::nextInt); - List numberList = generateRandom.limit(10000000).collect(Collectors.toList()); - - // 串行 - 把一千万个随机数,每个随机数 * 2 ,然后求和 - long start = System.currentTimeMillis(); - int sum = numberList.stream() - .map(number -> number * 2) - .mapToInt(x -> x) - .sum(); - long end = System.currentTimeMillis(); - System.out.println("串行耗时:"+(end - start)+"ms,和是:"+sum); - - // 并行 - 把一千万个随机数,每个随机数 * 2 ,然后求和 - start = System.currentTimeMillis(); - sum = numberList.parallelStream() - .map(number -> number * 2) - .mapToInt(x -> x) - .sum(); - end = System.currentTimeMillis(); - System.out.println("并行耗时:"+(end - start)+"ms,和是:"+sum); - } -``` - -得到如下输出。 - -```java -串行耗时:1005ms,和是:481385106 -并行耗时:47ms,和是:481385106 -``` - -效果显而易见,代码简洁优雅。 - -## 5. Stream 流建议 - -### 5.1 保证正确排版 - -从上面的使用案例中,可以发现使用 `Stream` 流操作的代码非常简洁,而且可读性更高。但是如果不正确的排版,那么看起来将会很糟糕,比如下面的同样功能的代码例子,多几层操作呢,是不是有些让人头大? - -```java -// 不排版 -String string = names.stream().filter(num -> num.length() == 4).map(name -> name.toUpperCase()).collect(Collectors.joining(",")); -// 排版 -String string = names.stream() - .filter(num -> num.length() == 4) - .map(name -> name.toUpperCase()) - .collect(Collectors.joining(",")); -``` - -### 5.2 保证函数纯度 - -如果想要你的 `Stream` 流对于每次的相同操作的结果都是相同的话,那么你必须保证 `Lambda` 表达式的纯度,也就是下面两点。 - -- Lambda 中不会更改任何元素。 -- Lambda 中不依赖于任何可能更改的元素。 - -这两点对于保证函数的幂等非常重要,不然你程序执行结果可能会变得难以预测,就像下面的例子。 - -```java -@Test -public void simpleTest(){ - List numbers = Arrays.asList(1, 2, 3); - int[] factor = new int[] { 2 }; - Stream stream = numbers.stream() - .map(e -> e * factor[0]); - factor[0] = 0; - stream.forEach(System.out::println); -} -// 输出结果 -// 0 -// 0 -// 0 -``` - - - -文中代码都已经上传到 [Github.com/niumoo/jdk-feature ](https://github.com/niumoo/jdk-feature)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-08-feature-time.md b/docs/jdk/java-08-feature-time.md deleted file mode 100644 index 8483f6c..0000000 --- a/docs/jdk/java-08-feature-time.md +++ /dev/null @@ -1,305 +0,0 @@ ---- -title: Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下? -# toc: false -date: 2019-10-24 08:01:01 -url: jdk/jdk8-time -tags: - - Java8 - - LocalDateTime - - LocalDate -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -当前时间:2019年10月24日。距离 JDK 14 发布时间(2020年3月17日)还有多少天? - -```java -// 距离JDK 14 发布还有多少天? -LocalDate jdk14 = LocalDate.of(2020, 3, 17); -LocalDate nowDate = LocalDate.now(); -System.out.println("距离JDK 14 发布还有:"+nowDate.until(jdk14,ChronoUnit.DAYS)+"天"); -``` - -JDK 8 已经在 2014年 3月 18日正式可用 ,距离现在已经 5年多时间过去了。5年时间里很多企业也都换上了 JDK 8,明年 3月份 Jdk14 也要来了,那么 Jdk 8 的新特性你真的用起来了吗?我准备写一个 Jdk 8开始的新特性介绍以及使用的系列文章,后续 Jdk 也会继续更新,你如果需要的话不妨关注下博客或者公众号。 - -## 1. 时间处理类 - -Jdk8 带来了全新的时间处理工具类,用于代替之前存在缺陷的时间处理。新的时间处理相比之前更加简单好用。 - -![Jdk8 时间处理类](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571847428464-1571848139345.png) - -常用的类有以下几个类。 - -| 时间相关类 | 介绍 | -| ----------------- | -------------------------- | -| LocalDateTime | 时间处理类,最高精确到纳秒 | -| LocalDate | 时间处理类,最高精确到天 | -| DateTimeFormatter | 时间格式化 | -| ZoneId | 时区设置类 | - -## 2. 时间获取 - -使用不同的类可以获取不同精度的时间。 - -```java -/** - * 时间获取 -*/ -@Test -public void nowTimeTest() { - // 当前精确时间 - LocalDateTime now = LocalDateTime.now(); - System.out.println("当前精确时间:" + now); - System.out.println("当前精确时间:" + now.getYear() + "-" + now.getMonthValue() + "-" + now.getDayOfMonth() + " " + now.getHour() + "-" + now.getMinute() + "-" + now.getSecond()); - - // 获取当前日期 - LocalDate localDate = LocalDate.now(); - System.out.println("当前日期:" + localDate); - System.out.println("当前日期:" + localDate.getYear() + "-" + localDate.getMonthValue() + "-" + localDate.getDayOfMonth()); - - // 获取当天时间 - LocalTime localTime = LocalTime.now(); - System.out.println("当天时间:" + localTime); - System.out.println("当天时间:" + localTime.getHour() + ":" + localTime.getMinute() + ":" + localTime.getSecond()); - - // 有时区的当前精确时间 - ZonedDateTime nowZone = LocalDateTime.now().atZone(ZoneId.systemDefault()); - System.out.println("当前精确时间(有时区):" + nowZone); - System.out.println("当前精确时间(有时区):" + nowZone.getYear() + "-" + nowZone.getMonthValue() + "-" + nowZone.getDayOfMonth() + " " + nowZone.getHour() + "-" + nowZone.getMinute() + "-" + nowZone.getSecond()); -} -``` - -获取到的时间: - -```shell -当前精确时间:2019-10-24T00:26:41.724 -当前精确时间:2019-10-24 0-26-41 -当前日期:2019-10-24 -当前日期:2019-10-24 -当前精确时间(有时区):2019-10-24T00:26:41.725+08:00[GMT+08:00] -当前精确时间(有时区):2019-10-24 0-26-41 -当天时间:00:26:41.725 -当天时间:0:26:41 -``` - -## 3. 时间创建 - -可以指定年月日时分秒创建一个时间类,也可以使用字符串直接转换成时间。 - -```java -/** - * 时间创建 - */ -@Test -public void createTime() { - LocalDateTime ofTime = LocalDateTime.of(2019, 10, 1, 8, 8, 8); - System.out.println("当前精确时间:" + ofTime); - - LocalDate localDate = LocalDate.of(2019, 10, 01); - System.out.println("当前日期:" + localDate); - - LocalTime localTime = LocalTime.of(12, 01, 01); - System.out.println("当天时间:" + localTime); -} -``` - -创建的时间: - -```shell -当前精确时间:2019-10-01T08:08:08 -当前日期:2019-10-01 -当天时间:12:01:01 -``` - -## 4. 时间转换 - -```java -/** -* 日期转换 -*/ -@Test -public void convertTimeTest() { - LocalDateTime parseTime = LocalDateTime.parse("2019-10-01T22:22:22.222"); - System.out.println("字符串时间转换:" + parseTime); - - LocalDate formatted = LocalDate.parse("20190101", DateTimeFormatter.BASIC_ISO_DATE); - System.out.println("字符串时间转换-指定格式:" + formatted); - - // Date 转换成 LocalDateTime - Date date = new Date(); - ZoneId zoneId = ZoneId.systemDefault(); - System.out.println("Date 转换成 LocalDateTime:" + LocalDateTime.ofInstant(date.toInstant(), zoneId)); - - // LocalDateTime 转换成 Date - LocalDateTime localDateTime = LocalDateTime.now(); - Date toDate = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant()); - System.out.println("LocalDateTime 转换成 Date:" + toDate);\ - - // 当前时间转时间戳 - long epochMilli = LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli(); - System.out.println("当前时间转时间戳:" + epochMilli); - // 时间戳转换成时间 - LocalDateTime epochMilliTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(epochMilli), ZoneId.systemDefault()); - System.out.println("时间戳转换成时间:" + epochMilliTime); -} -``` - -转换结果: - -```shell -字符串时间转换:2019-10-01T22:22:22.222 -字符串时间转换-指定格式:2019-01-01 -Date 转换成 LocalDateTime:2019-10-24T00:46:41.251 -LocalDateTime 转换成 Date:Thu Oct 24 00:46:41 GMT+08:00 2019 -当前时间转时间戳:1571849201258 -时间戳转换成时间:2019-10-24T00:46:41.258 -``` - -## 5. 时间格式化 - -```java -/** - * 日期格式化 - */ -@Test -public void formatTest() { - LocalDateTime now = LocalDateTime.now(); - System.out.println("当前时间:" + now); - System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_DATE)); - System.out.println("格式化后:" + now.format(DateTimeFormatter.ISO_LOCAL_TIME)); - System.out.println("格式化后:" + now.format(DateTimeFormatter.ofPattern("YYYY-MM-dd hh:mm:ss"))); -} -``` - -格式化后: - -```shell -当前时间:2019-10-24T00:37:44.867 -格式化后:2019-10-24T00:37:44.867 -格式化后:2019-10-24 -格式化后:00:37:44.867 -格式化后:2019-10-24 12:37:44 -``` - -## 6. 时间比较 - -```java -/** - * 时间比较 - */ -@Test -public void diffTest() { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime yestory = now.minusDays(1); - System.out.println(now + "在" + yestory + "之后吗?" + now.isAfter(yestory)); - System.out.println(now + "在" + yestory + "之前吗?" + now.isBefore(yestory)); - - // 时间差 - long day = yestory.until(now, ChronoUnit.DAYS); - long month = yestory.until(now, ChronoUnit.MONTHS); - long hours = yestory.until(now, ChronoUnit.HOURS); - long minutes = yestory.until(now, ChronoUnit.MINUTES); - System.out.println("相差月份" + month); - System.out.println("相差天数" + day); - System.out.println("相差小时" + hours); - System.out.println("相差分钟" + minutes); - - // 距离JDK 14 发布还有多少天? - LocalDate jdk14 = LocalDate.of(2020, 3, 17); - LocalDate nowDate = LocalDate.now(); - System.out.println("距离JDK 14 发布还有:" + nowDate.until(jdk14, ChronoUnit.DAYS) + "天"); -} -``` - -比较结果: - -```shell -2019-10-24T00:39:01.589在2019-10-23T00:39:01.589之后吗?true -2019-10-24T00:39:01.589在2019-10-23T00:39:01.589之前吗?false -相差月份0 -相差天数1 -相差小时24 -相差分钟1440 -距离JDK 14 发布还有:145天 -``` - -## 7. 时间加减 - -```java -/** - * 日期加减 - */ -@Test -public void calcTest() { - LocalDateTime now = LocalDateTime.now(); - System.out.println("当前时间:"+now); - LocalDateTime plusTime = now.plusMonths(1).plusDays(1).plusHours(1).plusMinutes(1).plusSeconds(1); - System.out.println("增加1月1天1小时1分钟1秒时间后:" + plusTime); - LocalDateTime minusTime = now.minusMonths(2); - System.out.println("减少2个月时间后:" + minusTime); -} -``` - -操作结果: - -```shell -当前时间:2019-10-24T00:41:08.877 -增加1月1天1小时1分钟1秒时间后:2019-11-25T01:42:09.877 -减少2个月时间后:2019-08-24T00:41:08.877 -``` - -## 8. 时间扩展方法 - -```java -/** - * 时间方法 - */ -@Test -public void timeFunctionTest() { - LocalDateTime now = LocalDateTime.now(); - System.out.println("当前时间:" + now); - // 第一天 - LocalDateTime firstDay = now.withDayOfMonth(1); - System.out.println("本月第一天:" + firstDay); - // 当天最后一秒 - LocalDateTime lastSecondOfDay = now.withHour(23).withMinute(59).withSecond(59); - System.out.println("当天最后一秒:" + lastSecondOfDay); - // 最后一天 - LocalDateTime lastDay = now.with(TemporalAdjusters.lastDayOfMonth()); - System.out.println("本月最后一天:" + lastDay); - // 是否闰年 - System.out.println("今年是否闰年:" + Year.isLeap(now.getYear())); -} -``` - -输出结果: - -```java -当前时间:2019-10-24T00:43:28.296 -本月第一天:2019-10-01T00:43:28.296 -当天最后一秒:2019-10-24T23:59:59.296 -本月最后一天:2019-10-31T00:43:28.296 -今年是否闰年:false -``` - -Jdk 8 新的时间类使用起来相比之前显得更加方便简单。 - -![JDK8 之前时间处理](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571850210772.png) - -Jdk 8 也把时间处理成独立成一个包,并且使用不同的类名加以区分。而不是像之前相同的类名不同的包。这样的方式使用起来也更加清晰。 - -🚀 代码已经上传到 [Github(https://github.com/niumoo/jdk-feature)](https://github.com/niumoo/jdk-feature) 。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-09-feature.md b/docs/jdk/java-09-feature.md deleted file mode 100644 index d737bc9..0000000 --- a/docs/jdk/java-09-feature.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -title: Jdk14 都要出了,Jdk9 的新特性还不了解一下? -# toc: false -date: 2020-02-19 08:01:01 -url: jdk/jdk9-feature -tags: - - Java9 -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Java 9](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/140916143852191.png) - -`Java 9` 中最大的亮点是 **Java 平台模块化**的引入,以及模块化 JDK。但是 `Java 9` 还有很多其他新功能,这篇文字会将重点介绍开发人员特别感兴趣的几种功能。 - -这篇文章也是 Java 新特性系列文章中的一篇,往期文章可以查看下面链接。 - -[还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下](https://www.wdbyte.com/2019/11/jdk/jdk8-stream/) - -[还看不懂同事的代码?Lambda 表达式、函数接口了解一下](https://www.wdbyte.com/2019/11/jdk/jdk8-lambda/) - -[Jdk14 都要出了,还不能使用 Optional优雅的处理空指针?](https://www.wdbyte.com/2019/11/jdk/jdk8-optional/) - -[Jdk14 都要出了,Jdk8 的时间处理姿势还不了解一下?](https://www.wdbyte.com/2019/10/jdk/jdk8-time/) - -[还看不懂同事代码?快来补一波 Java 7 语法特性](https://www.wdbyte.com/2020/01/jdk/jdk7-start/) - - - -## 1. 模块化 - -`Java 9` 中的**模块化**是对 `Java` 的一次重大改进。但是**模块化**并不是最近才提出来的,我们经常使用的 `maven` 构建工具,就是典型的模块化构建工具。**模块化**不仅让模块命名清晰,写出高内聚低耦合的代码,更可以方便处理模块之间的调用关系。 - -![Java 9 模块系统](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/Explicit-modules.png) - -在 Oracle 官方中为 `Java 9` 中的模块系统的定义如下: - -> the module, which is a named, self-describing collection of code and data. This module system. - -直白翻译:模块是一个命名的,自我描述的代码和数据的集合。 - -`Java 9` 不仅支持了模块化开发,更是直接把 `JDK` 自身进行了模块化处理。`JDK` 自身的模块化可以带来很多好处,比如: - -- 方便管理,越来越大的 JDK 在模块化下结构变得更加清晰。 -- 模块化 JDK 和 JRE 运行时镜像可以提高性能、安全性、维护性。 -- 可以定制 JRE,使用更小的运行时镜像,比如网络应用不需要 swing 库,可以在打包时选择不用,减少性能消耗。 -- 清晰明了的模块调用关系,避免调用不当出来的各种问题。 - -上面提到了 JDK 自身的模块化,我们通过浏览 JDK 9 的目录结构也可以发现一些变化。 - -![JDK 模块化](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200217111754648.png) - -最明显的是在 JDK 9 中 jre 文件夹不存在了。下面是在 IDEA 中查看的 JDK 9 的依赖,命名规范的模块看起来是不是让人赏心悦目呢? - -![JDK 9 在 IDEA](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200217112025069.png) - -当然,这篇文章主要介绍 Java 9 的新特性,而模块化是一个巨大改变,结合示例介绍下来篇幅会比较长,这里就不占用太多篇幅了。 - -模块化文章预告:如何编写一个模块化系统,如何打包让没有安装 Java 环境的系统运行编写的代码,都可以通过模块化选择运行时模块实现。我后面的文章就会通过一个模块化项目介绍到,有兴趣的可以关注我后续文章 😎。 - -## 2. 集合工厂方法 - -在 Java 9 中为集合的创建增加了静态工厂创建方式,也就是 `of` 方法,通过静态工厂 `of` 方法创建的集合是**只读集合**,里面的对象**不可改变**。并在**不能存在 `null` 值**,对于 `set` 和 `map` 集合,也**不能存在 `key` 值重复**。这样不仅**线程安全**,而且**消耗的内存也更小**。 - -下面是三种集合通过静态工厂创建的方式。 - -```java -// 工厂方法创建集合 -List stringList = List.of("a", "b", "c", "d"); -Set stringSet = Set.of("a", "b", "c", "d"); -Map stringIntegerMap = Map.of("key1", 1, "key2", 2, "key3", 3); -Map stringIntegerMap2 = Map.ofEntries(Map.entry("key1", 1), Map.entry("key2", 2)); - -// 集合输出 -System.out.println(stringList); -System.out.println(stringSet); -System.out.println(stringIntegerMap); -System.out.println(stringIntegerMap2); -``` - -得到输出结果。 - -``` shell -[a, b, c, d] -[d, a, c, b] -{key2=2, key1=1, key3=3} -{key2=2, key1=1} -``` - -再次运行,得到输出结果。 - -```shell -[a, b, c, d] -[a, c, b, d] -{key3=3, key2=2, key1=1} -{key2=2, key1=1} -``` - -为什么我贴了两次运行结果呢?主要是要展示通过 `of` 方法创建的 `set` 和 `map` 集合在遍历时,在每个 JVM 周期遍历顺序是随机的,这样的机制可以发下代码中有没有对于顺序敏感的异常代码。 - -这种只读集合在 Java 9 之前创建是通过 `Collections.unmodifiableList` 修改集合操作权限实现的。 - -```java -List arrayList = new ArrayList<>(); -arrayList.add("达西"); -arrayList.add("未读代码"); -// 设置为只读集合 -arrayList = Collections.unmodifiableList(arrayList); -``` - -静态工厂 `of` 方法创建的集合还有一个特性,就是工厂内部会自由复用已有实例或者创建新的实例,所以应该避免对 `of` 创建的集合进行判等或者 `haseCode` 比较等操作。 - -像下面这样,创建两个 `List`,你会发现两个 `List` 的 `hashCode` 是一样的。 - -```java -// 工厂可以自由创建新的实例或者复用现有实例,所以 使用 of 创建的集合,避免 == 或者 hashCode 判断操作 -List stringList = List.of("a", "b", "c", "d"); -List stringList2 = List.of("a", "b", "c", "d"); -System.out.println(stringList.hashCode()); -System.out.println(stringList2.hashCode()); -// 输出结果 -// 3910595 -// 3910596 -``` - -这也是使用 `of` 方法创建集合的优势之一,消耗更少的系统资源。这一点也体现在 `of` 创建的集合的数据结构实现上,有兴趣的同学可以自行研究下。 - -## 3. Stream API - -`Stream` 流操作自从 `Java 8` 引入以来,一直广受好评。便捷丰富的 `Stream` 操作让人爱不释手,更让没看过的同事眼花缭乱,在介绍 `Java 8` 新特性时已经对 `Stream` 进行了详细的介绍,没看过的同学可以看下这篇: - -[还看不懂同事的代码?超强的 Stream 流操作姿势还不学习一下](https://www.wdbyte.com/2019/11/jdk/jdk8-stream/) - -当然,学习 `Stream` 之前要先学习 `Lambda` ,如果你还没有看过,也可以看下之前这篇: - -[还看不懂同事的代码?Lambda 表达式、函数接口了解一下](https://www.wdbyte.com/2019/11/jdk/jdk8-lambda/) - -在 `Java 9` 中,又对 `Stream` 进行了增强,主要增加了 4 个新的操作方法:*dropWhile,takeWhile,ofNullable,iterate*。 - -下面对这几个方法分别做个介绍。 - -1. takeWhile: 从头开始筛选,遇到不满足的就结束了。 - - ```java - // takeWhile ,从头开始筛选,遇到不满足的就结束了 - List list1 = List.of(1, 2, 3, 4, 5); - List listResult = list1.stream().takeWhile(x -> x < 3).collect(Collectors.toList()); - System.out.println(listResult); - // takeWhile ,从头开始筛选,遇到不满足的就结束 - List list2 = List.of(1, 2, 3, 4, 3, 0); - List listResult2 = list2.stream().takeWhile(x -> x < 3).collect(Collectors.toList()); - System.out.println(listResult2); - ``` - - 输出结果。 - - ```shell - [1, 2] - [1, 2] - ``` - -2. dropWhile: 从头开始删除,遇到不满足的就结束了。 - - ```java - // dropWhile ,从头开始删除,遇到不满足的就结束了 - List list1 = List.of(1, 2, 3, 4, 5); - List listResult = list1.stream().dropWhile(x -> x < 3).collect(Collectors.toList()); - System.out.println(listResult); - // dropWhile ,从头开始删除,遇到不满足的就结束 - List list2 = List.of(1, 2, 3, 4, 3, 0); - List listResult2 = list2.stream().dropWhile(x -> x < 3).collect(Collectors.toList()); - System.out.println(listResult2); - ``` - - 输出结果。 - - ```shell - [3, 4, 5] - [3, 4, 3, 0] - ``` - -3. ofNullable: 创建支持全 null 的 Stream. - - ```java - Stream stream = Stream.of(1, 2, null); - stream.forEach(System.out::print); - System.out.println(); - // 空指针异常 - // stream = Stream.of(null); - stream = Stream.ofNullable(null); - stream.forEach(System.out::print); - ``` - - 输出结果。 - - ```shell - 12null - ``` - -4. iterate: 可以重载迭代器。 - - ```java - IntStream.iterate(0, x -> x < 10, x -> x + 1).forEach(System.out::print); - ``` - - 输出结果。 - - ```shell - 0123456789 - ``` - -在 `Stream` 增强之外,还增强了 `Optional` ,`Optional` 增加了可以转换成 `Stream` 的方法。 - -```java -Stream s = Optional.of(1).stream(); -s.forEach(System.out::print); -``` - -## 4. 接口私有方法 - -在 `Java 8 ` 中增加了默认方法,在 `Java 9 ` 中又增加了私有方法,这时开始接口中不仅仅有了定义,还具有了行为。我想这是出于代码构造上的考虑,如果没有私有方法,那么当多个默认方法的行为一样时,就要写多个相同的代码。而有了私有方法,事情就变得不一样了。 - -就像下面的例子。 - -```java -/** - * @author 达西 - 公众号:未读代码 - */ -public class Jdk9Interface { - public static void main(String[] args) { - ChinaPeople chinaPeople = new ChinaPeople(); - chinaPeople.sleep(); - chinaPeople.eat(); - chinaPeople.doXxx(); - } - -} - -class ChinaPeople implements People { - @Override - public void sleep() { - System.out.println("躺着睡"); - } -} - -interface People { - void sleep(); - - default void eat() { - drink(); - } - - default void doXxx() { - drink(); - } - - private void drink() { - System.out.println("喝水"); - } -} -``` - -例子中的接口 `people` 中的 `eat()` 和 `doXxx()` 默认行为一致,使用私有方法可以方便的抽取一个方法出来。 - -输出结果。 - -```shell -躺着睡 -喝水 -喝水 -``` - -## 5. HTTP / 2 Client - -`Java 9` 内置了新的 HTTP/2 客户端,请求更加方便。 - -随便访问一个不存在的网页。 - -```java -HttpClient client = HttpClient.newHttpClient(); -URI uri = URI.create("http://www.tianqiapi.com/api/xxx"); -HttpRequest req = HttpRequest.newBuilder(uri).header("User-Agent", "Java").GET().build(); -HttpResponse resp = client.send(req, HttpResponse.BodyHandler.asString()); -String body = resp.body(); -System.out.println(body); -``` - -输出得到的结果,这里是这个网站的报错信息。 - -```java -There is no method xxxAction in ApiController -``` - -可能你运行的时候会报找不到 `httpClient` 模块之类的问题,这时候需要你在你项目代码目录添加 `httpClient 模块` 才能解决,添加方式看下面的图。 - -![Java 9 导入导出模块](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200217225456437.png) - -`export ` 写自己的包路径,`requires` 写引入的模块名。 - - - -## 6. Java REPL - JShell - -交互式的编程环境在其他语言如 Python 上早就有了,而 Java 上的交互式语言只到 `Java 9`才出现。交互式的编程可以让开发者在输入代码的时候就获取到程序的运行结果,而不用像之前一样新建文件、创建类、导包、测试一系列流程。 - -`JShell` 中支持 `tab` 补全代码以及自动添加分号,下面通过一个例子演示 `JShell` 的使用。 - -1. 进入 JShell. 查看帮助文档。 - - ```shell - C:\Users>jshell - | 欢迎使用 JShell -- 版本 9 - | 要大致了解该版本, 请键入: /help intro - jshell> /help - | 键入 Java 语言表达式, 语句或声明。 - | 或者键入以下命令之一: - | /list [<名称或 id>|-all|-start] - | 列出您键入的源 - | /edit <名称或 id> - | 编辑按名称或 id 引用的源条目 - | /drop <名称或 id> - | 删除按名称或 id 引用的源条目 - | /save [-all|-history|-start] <文件> - | 将片段源保存到文件。 - | /open - | 打开文件作为源输入 - | /vars [<名称或 id>|-all|-start] - | 列出已声明变量及其值 - | /methods [<名称或 id>|-all|-start] - | 列出已声明方法及其签名 - | /types [<名称或 id>|-all|-start] - | 列出已声明的类型 - | /imports - | 列出导入的项 - | /exit - | 退出 jshell - | /env [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>] ... - | 查看或更改评估上下文 - | /reset [-class-path <路径>] [-module-path <路径>] [-add-modules <模块>]... - | 重启 jshell - | /reload [-restore] [-quiet] [-class-path <路径>] [-module-path <路径>]... - | 重置和重放相关历史记录 -- 当前历史记录或上一个历史记录 (-restore) - | /history - | 您键入的内容的历史记录 - | /help [|] - | 获取 jshell 的相关信息 - | /set editor|start|feedback|mode|prompt|truncation|format ... - | 设置 jshell 配置信息 - | /? [|] - | 获取 jshell 的相关信息 - | /! - | 重新运行上一个片段 - | / - | 按 id 重新运行片段 - | /- - | 重新运行前面的第 n 个片段 - | - | 有关详细信息, 请键入 '/help', 后跟 - | 命令或主题的名称。 - | 例如 '/help /list' 或 '/help intro'。主题: - | - | intro - | jshell 工具的简介 - | shortcuts - | 片段和命令输入提示, 信息访问以及 - | 自动代码生成的按键说明 - | context - | /env /reload 和 /reset 的评估上下文选项 - - jshell> - ``` - -2. 定义一个变量:a = 10,遍历从 0 到 a 的数字。 - - ```java - jshell> int a =10; - a ==> 10 - jshell> for(int i=0;i List list = List.of(1,2,3,4,5); - list ==> [1, 2, 3, 4, 5] - jshell> list - list ==> [1, 2, 3, 4, 5] - ``` - -4. 查看输入过的代码。 - - ```java - jshell> /list - 1 : int a =10; - 2 : for(int i=0;i /imports - | import java.io.* - | import java.math.* - | import java.net.* - | import java.nio.file.* - | import java.util.* - | import java.util.concurrent.* - | import java.util.function.* - | import java.util.prefs.* - | import java.util.regex.* - | import java.util.stream.* - ``` - -6. 将代码保存到文件并退出。 - - ```java - jshell> /save d:/JShell.java - jshell> /exit - 再见 - ``` - - 在 D 盘看到的保存的代码片段。 - - ![JShell 保存的代码](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200217232617028.png) - - - -操作起来还是挺简单的,还记得上面介绍集合工厂 `of` 方法创建出来的 `set` 和 `map` 数据在每个 `JVM` 周期里是无序的嘛?也可以用 `JShell` 实验下。 - -![Set.of 的随机遍历](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200218113246293.png) - -## 7. 其他更新 - -`Java 9` 中增加或者优化的功能远不止这些,上面只是列举了常用的一些新特性,更多的新特性如: - -- 不能使用下划线 _ 作为变量名,因为它是一个关键字。 -- Javadoc 支持 HTML5 并且支持搜索功能。 -- Nashorn 引擎升级,更好的支持 Javascript. -- String 存储结构变更从 char -> byte. -- ......... - -新特性很多,感兴趣的可以自己了解下。 - -**再次预告**,后续文章会结合案例图文并茂详细介绍 **Java 9 开始的模块系统**,感兴趣的可以关注我。此去山高水远,愿你我一路同行。 - -文章案例都已经上传到 Github:[niumoo/jdk-feature](https://github.com/niumoo/jdk-feature) - -**参考资料** - -- [Java Platform, Standard Edition What’s New in Oracle JDK 9](https://docs.oracle.com/javase/9/whatsnew/toc.htm) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-10-feature.md b/docs/jdk/java-10-feature.md deleted file mode 100644 index fb28f08..0000000 --- a/docs/jdk/java-10-feature.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -title: 最通俗易懂的 Java 10 新特性讲解 -# toc: false -date: 2020-02-27 08:01:01 -url: jdk/jdk10-feature -tags: - - Java10 -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Java 10](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/Whats_New_Java10-881x441.png) - -自从 `Java 9` 开始,Oracle 调整了 Java 版本的发布策略,不再是之前的 N 年一个大版本,取而代之的是 6 个月一个小版本,三年一个大版本,这样可以让 Java 的最新改变迅速上线,而小版本的维护周期缩短到下个版本发布之前,大版本的维护周期则是 3 年之久。而 10 就是这么一个小版本,因为 Java 的后续版本基本都会包含之前新特性,所以还是把 `Java 10` 带来的改变单独写一写。 - - - -## 1. JEP 322 - 基于时间的版本号 - -就像上面说的,Java 调整了发布策略,为了适应这种发布节奏,随着改变的还有 Java 版本号的记录方式。 - -版本号的新模式是:`$FEATURE.$INTERIM.$UPDATE.$PATCH` - -- `$FEATURE` :基于发布版本,如 Java 10 的 10 。 -- `$INTERIM` :问题修复和功能增强时 + 1,默认是 0 。 -- `$UPDATE` :在进行兼容更新,修复新功能安全问题时 +1。 -- `$PATCH` :特殊问题修复时 +1。 - -查看自己的 `Java 10 ` 版本。 - -```java -$ java -version -java version "10.0.1" 2018-04-17 -Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10) -Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode) -``` - -## 2. JEP 286 - 局部类型推断 - -JEP 286 提案让 Java 增加了局部类型推断(Local-Variable Type Inference)功能,这让 Java 可以像 `Js` 里的 `var` 或者其他语言的 `auto` 一样可以自动推断数据类型。这其实只是一个新的语法糖,底层并没有变化,在编译时就已经把 `var` 转化成具体的数据类型了,但是这样可以减少代码的编写。 - -你可以像下面这样使用 `var` 语法。 - -```java -var hashMap = new HashMap(); -hashMap.put("微信","wn8398"); -var string = "hello java 10"; -var stream = Stream.of(1, 2, 3, 4); -var list = new ArrayList(); -``` - -如果你反编译编译后的这段代码,你会发现还是熟悉的代码片段。 - -```java -HashMap hashMap = new HashMap(); -hashMap.put("微信", "wn8398"); -String string = "hello java 10"; -Stream stream = Stream.of(1, 2, 3, 4); -ArrayList list = new ArrayList(); -``` - -`var` 看似好用,其实也有很多限制,官方介绍了 `var` 只能用于下面的几种情况。 - -1. 仅限带有初始化的程序的局部变量。 -2. `for` 循环或者`增强for` 循环中。 -3. `for`循环中的声明。 - -下面演示三种使用情况。 - -```java -public static void testVar() { - // 情况1,没有初始化会报错 - // var list; - var list = List.of(1, 2, 3, 4); - // 情况2 - for (var integer : list) { - System.out.println(integer); - } - // 情况3 - for (var i = 0; i < list.size(); i++) { - System.out.println(list.get(i)); - } -} -``` - -尽管对 `var` 的使用场景增加了很多限制,但在实际使用时你还是要注意,就像下面的代码,你可能一眼并不能看出 `result` 的数据类型。 - -```java -var query = "xxx"; -var result = dbUtil.executeQuery(query); -``` - - - -## 3. JEP 317 - 基于 Java 的 JIT 编译器(实验性) - -这个功能让基于 Java 开发的 JIT 编译器 `Graal` 结合 `Java 10` 用在 Linux / x64 平台上,这是一个实验性的 JIT 编译器,有人说这也是 `Java 10` 中最具有未来感的引入。Graal 其实在 `Java 9 ` 中就已经引入了,它带来了 Java 中的 AOT (Ahead Of Time)编译,还支持多种语言,如 Js、Python、Ruby、R、以及其他基于 JVM (如 Java、Kotlin)的和基于 LLVM (如 C、C++)的语言。 - -想切换到 `Graal` 可以使用下面的 `jvm` 参数。 - -```shell --XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler -``` - -这里面有一点我觉得很有意思,看这个图。 - -![Graal 由 Java 编写](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20200223223401909.png) - -这就很有意思了,`Graal` 是 Java 语言编写的,用 Java 编写的编译器,然后用来将 Java 字节码编译机器代码。 - -`Graal` 官网:[https://www.graalvm.org/](https://www.graalvm.org/) - -## 4. JEP 310 - 类数据共享 - -JVM 启动时有一步是需要在内存中加载类,而如果有多个 jar,加载第一个 jar 的速度是最慢的。这就延长了程序的启动时间,为了减少这个时间,`Java 10` 引入了应用程序类数据共享(CDS)机制,它可以把你想共享的类共享在程序之间,使不同的 Java 进程之间共享这个类来减少这个类占用的空间以及加载速度。 - -## 5. JEP 307 - G1 并行全GC - -早在 `Java 9` 时就已经引入了 G1 垃圾收集器,G1 的优点很多。而在 `Java 10` 中还是做了小小调整,当 G1 的并发收集线程不能快速的完成全 GC 时,就会自动切换到**并行**收集,这可以减少在最坏情况下的 GC 速度。 - -## 6. JEP 314 - Unicode 语言标签扩展 - -这个提案让 JDK 实现了最新的 [LDML 规范](http://www.unicode.org/reports/tr35/tr35.html#Locale_Extension_Key_and_Type_Data)中指定的更多的扩展。 - -主要增加了下面几个扩展方法。 - -```java -java.time.temporal.WeekFields::of -java.util.Calendar::{getFirstDayOfWeek,getMinimalDaysInWeek} -java.util.Currency::getInstance -java.util.Locale::getDisplayName -java.util.spi.LocaleNameProvider -java.text.DateFormat::get*Instance -java.text.DateFormatSymbols::getInstance -java.text.DecimalFormatSymbols::getInstance -java.text.NumberFormat::get*Instance -java.time.format.DateTimeFormatter::localizedBy -java.time.format.DateTimeFormatterBuilder::getLocalizedDateTimePattern -java.time.format.DecimalStyle::of -``` - -尝试一下。 - -```java -Currency chinaCurrency = Currency.getInstance(Locale.CHINA); -Currency usCurrency = Currency.getInstance(Locale.US); -System.out.println("本地货币:" + chinaCurrency); -System.out.println("US.货币:" + usCurrency); - -String displayName = Locale.getDefault().getDisplayName(); -String displayLanguage = Locale.getDefault().getDisplayLanguage(); -String displayCountry = Locale.getDefault().getDisplayCountry(); -System.out.println("本地名称:" + displayName); -System.out.println("本地语言:" + displayLanguage); -System.out.println("本地国家:" + displayCountry); -int firstDayOfWeek = Calendar.getInstance().getFirstDayOfWeek(); -System.out.println("本地每周第一天:" + firstDayOfWeek); -``` - -输出结果。 - -```shell -本地货币:CNY -US.货币:USD -本地名称:中文 (中国) -本地语言:中文 -本地国家:中国 -本地每周第一天:1 -``` - -## 7. API 更新 - -`Java 10` 删除了部分 API,也增加了一些实用方法。比如可以通过 `Collection.copyOf` 复制得到一个不可改变集合,即使原来的集合元素发生了变化也不会有影响。 - -```java -var list = new ArrayList(); -list.add("wechat"); -list.add("wn8398"); -List copyList = List.copyOf(list); -list.add("test"); -System.out.println(copyList); -// result -// [wechat, wn8398] -``` - -也为 `Optional` 增加了一个新的方法 `orElseThrow`。调用这个方法也可以获取到 `optional` 中的 `value` , 但是如果 `value` 为 `null` ,就会抛出异常。 - -另外在 `Stream` 最后收集数据的时候,`Collectors` 可以直接指定收集的集合为不可变集合,像下面这样。 - -```java -list.stream().collect(Collectors.toUnmodifiableList()); -list.stream().collect(Collectors.toUnmodifiableSet()); -``` - -## 其他更新 - -`Java 10` 的更新内容不止这些,上面只是列举了常用的以及比较有意思的新特性。还有部分更新如: - -1. JEP 312:Thread-Local Handshakes,JVM 内部功能,可以提高 JVM 性能。 -2. JEP 313:删除了 `javah` 工具,说是删除,其实功能已经包含在 `Java 8` 中的 `javac` 里。 -3. JEP 316:让 JVM 可以在备用的存储设备(如 NV-DIMM)上分配堆内存,而不用更改程序代码。 -4. JEP 319:在JDK中提供一组默认的根证书颁发机构(CA)证书。 - - -文章案例都已经上传到 Github:[niumoo/jdk-feature](https://github.com/niumoo/jdk-feature) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-11-feature.md b/docs/jdk/java-11-feature.md deleted file mode 100644 index e8d94de..0000000 --- a/docs/jdk/java-11-feature.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -title: 最通俗易懂的 Java 11 新特性讲解 -date: 2020-03-01 08:01:01 -url: jdk/jdk11-feature -tags: - - Java11 -categories: - - Java 新特性 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![图片来自网络,作者:manotang](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/Oxz5l3JUFaHrCNm.jpg) - -大多数开发者还是沉浸在 `Java 8` 中,而 `Java 14` 将要在 2020 年 3 月 17 日发布了,而我还在写着 `Java 11` 的新特性。`Java 11` 是 `Java 8` 之后的第一个 LTS 版本,但是也自从 `Java 11` 开始, Oracle JDK 不再可以免费的用于商业用途,当然如果你是个人使用,或者是使用 Open JDK ,那么还是可以免费使用的。 - -> 有些人很关心 `Java 11` 是否收费,Oracle 表示除非你在生产中使用,否则可以不用收费。 -> -> 即使收费,免费的 Open JDK 不也很香吗。 - -可免费用于生产环境的 Open JDK 官网:[https://jdk.java.net/11/](https://jdk.java.net/11/) - -再 6 个月后,`Java 15` 都要来了,这种发布节奏不仅让人有点应接不暇,更有点眼花缭乱。但是不管怎么说,发展的趋势不可逆,所以补习一波 `Java 11` 也是很有必要的。 - - - -## 1. String API - -字符串绝对是 Java 中最常用的一个类了,String 类的方法使用率也都非常的高,在 `Java 11` 中又为 String 类带来了一系列的好用操作。 - -1. isBlank() 判空。 - - ```java - // 判空,blank里我放入了全角空格,半角空格,TAB - String blank = "   "; - System.out.println(blank.isBlank()); - - // 输出 - // true - ``` - -2. lines() 分割获取字符串流。 - - ```java - // lines 返回一个 Stream - String line = "a\nb\nc"; - Stream lines = line.lines(); - // 使用 lambda 遍历 - lines.forEach(System.out::println); - - // 输出 - // a - // b - // c - ``` - -3. repeat() 复制字符串 - - ```java - // 复制字符串 - String repeat = "我的微信:wn8398,"; - String repeat3 = repeat.repeat(3); - System.out.println(repeat3); - - // 输出 - // 我的微信:wn8398,我的微信:wn8398,我的微信:wn8398, - ``` - -4. strip() 去除前后空白字符。 - - ```java - // 去除前后空白 - String strip = "   https://www.wdbyte.com  "; - System.out.println("==" + strip.trim() + "=="); - // 去除前后空白字符,如全角空格,TAB - System.out.println("==" + strip.strip() + "=="); - // 去前面空白字符,如全角空格,TAB - System.out.println("==" + strip.stripLeading() + "=="); - // 去后面空白字符,如全角空格,TAB - System.out.println("==" + strip.stripTrailing() + "=="); - - // 输出 - // ==  https://www.wdbyte.com  == - // ==https://www.wdbyte.com== - // ==https://www.wdbyte.com  == - // ==   https://www.wdbyte.com== - ``` - - 这里注意,`trim` 只能去除半角空格,而 `strip` 是**去除各种空白符**。 - -## 2. File API - -读写文件变得更加方便。 - -``` -// 创建临时文件 -Path path = Files.writeString(Files.createTempFile("test", ".txt"), "https://www.wdbyte.com"); -System.out.println(path); -// 读取文件 -// String ss = Files.readString(Path.of("file.json")); -String s = Files.readString(path); -System.out.println(s); - -// 结果 -// https://www.wdbyte.com -``` - -## 3. JEP 321 - HTTP Client - -在 `Java 11` 中 Http Client API 得到了标准化的支持。且支持 HTTP/1.1 和 HTTP/2 ,也支持 websockets。 - -你可以像这样发起一个请求。 - -```java -HttpClient client = HttpClient.newHttpClient(); -HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create("https://www.hao123.com")) - .build(); -// 异步 -client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenAccept(System.out::println) - .join(); - -// 同步 -HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); -System.out.println(response.body()); -``` - -更多的如同步异步请求,并发访问,设置代理等方式,可以参考 OpenJDK 官方文档。 - -[http://openjdk.java.net/groups/net/httpclient/recipes-incubating.html](http://openjdk.java.net/groups/net/httpclient/recipes-incubating.html) - -你现在还需要各种 HTTP Client 依赖包吗? - -## 4. JEP 323 - Lambda 局部变量推断 - -在 `Java 10` 中引入了 `var` 语法,可以自动推断变量类型。在 `Java 11` 中这个语法糖可以在 Lambda 表达式中使用了。 - -```java -var hashMap = new HashMap(); -hashMap.put("wechat", "wn8398"); -hashMap.put("website", "https://www.wdbyte.com"); -hashMap.forEach((var k, var v) -> { - System.out.println(k + ": " + v); -}); -``` - -这里需要注意的是,`(var k,var v)` 中,k 和 v 的类型要么都用 var ,要么都不写,要么都写正确的变量类型。而不能 var 和其他变量类型混用。 - -![Lambda 中 var 不能混用](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/Lgjh2n6qAr34lK8.jpg) - -## 5. JEP 330 - 单命令运行 Java - -自从学习 Java 的第一天,我们就知道运行一个 Java 文件,要先用 `javac` 命令编译,再用 `java` 命令运行,而现在只要一个 `java` 命令就可以运行了。 - -```shell -$ cat Main.java - -public class Main { - - public static void main(String[] args) { - System.out.println("wechat:wn8398"); - } -} - -$ java Main.java -wechat:wn8398 -``` - -## 6. 免费的飞行记录器 - -商业版 JDK 中一直有一款低开销的事件信息收集工具,也就是飞行记录器(Java Flight Recorder),它可以对 JVM 进行检查、分析、记录等。当出现未知异常时可以通过记录进行故障分析。这个好用的工具在 `Java 11` 中将开源免费。所有人都可以使用这个功能了。 - -## 其他更新 - -1. JEP 309 - 添加动态文件常量。 -2. JEP 318 - 添加 Epsilon 垃圾收集器。 -3. JEP 320 - 删除 Java EE 和 corba 模块(java.xml.ws, java.xml.bind, java.activation, java.xml.ws.annotation, java.corba, java.transaction, java.se.ee, jdk.xml.ws, jdk.xml.bind)。 -4. JEP 329 - 增加加密算法 chacha20,poly1305 的实现。 -5. JEP 333 - 引入实验性的 ZGC 垃圾收集器,保证停摆时间不会超过 10ms。 -6. JEP 335 - 废弃 Nashorn JavaScript 引擎 - -文章案例都已经上传到 Github:[niumoo/jdk-feature](https://github.com/niumoo/jdk-feature) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-src-arrayList-linkedList.md b/docs/jdk/java-src-arrayList-linkedList.md deleted file mode 100644 index eef145d..0000000 --- a/docs/jdk/java-src-arrayList-linkedList.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -title: 「源码分析」ArrayList和LinkedList如何实现的?我看你还有机会! -date: 2020-08-12 08:01:01 -url: jdk/src-arraylist-linkedlist -tags: - - List - - ArrayList - - LinkedList -categories: - - Java 源码分析 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 前言 -说真的,在 Java 使用最多的集合类中,List 绝对占有一席之地的,它和 Map 一样适用于很多场景,非常方便我们的日常开发,毕竟存储一个列表的需求随处可见。尽管如此,还是有很多同学没有弄明白 List 中 **ArrayList** 和 **LinkedList** 有什么区别,这简直太遗憾了,这两者其实都是数据结构中的**基础内容**,这篇文章会从**基础概念**开始,分析两者在 Java 中的**具体源码实现**,寻找两者的不同之处,最后思考它们使用时的注意事项。 - -这篇文章会包含以下内容。 - -1. 介绍线性表的概念,详细介绍线性表中**数组**和**链表**的数据结构。 -2. 进行 ArrayList 的源码分析,比如存储结构、扩容机制、数据新增、数据获取等。 -3. 进行 LinkedList 的源码分析,比如它的存储结构、数据插入、数据查询、数据删除和 LinkedList 作为队列的使用方式等。 -4. 进行 ArrayList 和 LinkedList 的总结。 - -## 线性表 - -要研究 **ArrayList** 和 **LinkedList** ,首先要弄明白什么是**线性表**,这里引用百度百科的一段文字。 -> 线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。 - -你肯定看到了,线性表在数据结构中是一种**最基本、最简单、最常用**的数据结构。它将数据一个接一个的排成一条线(可能逻辑上),也因此线性表上的每个数据只有前后两个方向,而在数据结构中,**数组、链表、栈、队列**都是线性表。你可以想象一下整整齐齐排队的样子。 - -![线性表](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200809004119875.png) - -看到这里你可能有疑问了,有线性表,那么肯定有**非线性表**喽?没错。**二叉树**和**图**就是典型的非线性结构了。不要被这些花里胡哨的图吓到,其实这篇文章非常简单,希望同学耐心看完**点个赞**。 - -![非线性接口(图片来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/grap.png) - -### 数组 - -即然知道了什么是线性表,那么理解数组也就很容易了,首先数组是线性表的一种实现。数组是由**相同类型**元素组成的一种数据结构,数组需要分配**一段连续的内存**用来存储。注意关键词,**相同类型**,**连续内存**,像这样。 - -![数组](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200810224700319.png) - -不好意思放错图了,像这样。 - -![数组概念](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200808232102227.png) - -上面的图可以很直观的体现数组的存储结构,因为数组内存地址连续,元素类型固定,所有具有**快速查找**某个位置的元素的特性;同时也因为数组需要一段连续内存,所以长度在初始化**长度已经固定**,且不能更改。Java 中的 **ArrayList** 本质上就是一个数组的封装。 - -### 链表 - -链表也是一种线性表,和数组不同的是链表**不需要连续的内存**进行数据存储,而是在每个节点里同时**存储下一个节点**的指针,又要注意关键词了,每个节点都有一个指针指向下一个节点。那么这个链表应该是什么样子呢?看图。 - -![单向链表](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200810224910849.png) - -哦不,放错图了,是这样。 - -![链表存储结构(图片来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200808233941445.png) - -上图很好的展示了链表的存储结构,图中每个节点都有一个指针指向下一个节点位置,这种我们称为**单向链表**;还有一种链表在每个节点上还有一个指针指向上一个节点,这种链表我们称为**双向链表**。图我就不画了,像下面这样。 - -![双向链表](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200810224500217.png) - -可以发现链表不必连续内存存储了,因为链表是通过节点指针进行下一个或者上一个节点的,只要找到头节点,就可以以此找到后面一串的节点。不过也因此,链表在**查找或者访问某个位置的节点**时,需要**O(n)**的时间复杂度。但是插入数据时可以达到**O(1)**的复杂度,因为只需要修改节点指针指向。 - - -## ArratList - -上面介绍了线性表的概念,并举出了两个线性表的实际实现例子,既数组和链表。在 Java 的集合类 ArrayList 里,实际上使用的就是数组存储结构,ArrayList 对 Array 进行了封装,并增加了方便的插入、获取、扩容等操作。因为 ArrayList 的底层是数组,所以存取非常迅速,但是增删时,因为要移动后面的元素位置,所以增删效率相对较低。那么它具体是怎么实现的呢?不妨深入源码一探究竟。 - -### ArrayList 存储结构 - -查看 ArrayList 的源码可以看到它就是一个简单的数组,用来数据存储。 - -```java -/** - * The array buffer into which the elements of the ArrayList are stored. - * The capacity of the ArrayList is the length of this array buffer. Any - * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA - * will be expanded to DEFAULT_CAPACITY when the first element is added. - */ -transient Object[] elementData; // non-private to simplify nested class access - -/** - * Shared empty array instance used for default sized empty instances. We - * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when - * first element is added. - */ -private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; - -/** - * Default initial capacity. - */ -private static final int DEFAULT_CAPACITY = 10; -``` - -通过上面的注释了解到,ArrayList 无参构造时是会共享一个长度为 0 的数组 DEFAULTCAPACITY_EMPTY_ELEMENTDATA. 只有当第一个元素添加时才会第一次扩容,这样也防止了创建对象时更多的内存浪费。 - -### ArrayList 扩容机制 - -我们都知道数组的大小一但确定是不能改变的,那么 ArrayList 明显可以不断的添加元素,它的底层又是数组,它是怎么实现的呢?从上面的 ArrayList 存储结构以及注释中了解到,ArrayList 在初始化时,是共享一个长度为 0 的数组的,当第一个元素添加进来时会进行第一次扩容,我们可以想像出 ArrayList 每当空间不够使用时就会进行一次扩容,那么扩容的机制是什么样子的呢? - -依旧从源码开始,追踪 add() 方法的内部实现。 - -```java -/** - * Appends the specified element to the end of this list. - * - * @param e element to be appended to this list - * @return true (as specified by {@link Collection#add}) - */ -public boolean add(E e) { - ensureCapacityInternal(size + 1); // Increments modCount!! - elementData[size++] = e; - return true; -} -// 开始检查当前插入位置时数组容量是否足够 -private void ensureCapacityInternal(int minCapacity) { - // ArrayList 是否未初始化,未初始化是则初始化 ArrayList ,容量给 10. - if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { - minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); - } - ensureExplicitCapacity(minCapacity); -} -// 比较插入 index 是否大于当前数组长度,大于就 grow 进行扩容 -private void ensureExplicitCapacity(int minCapacity) { - modCount++; - // overflow-conscious code - if (minCapacity - elementData.length > 0) - grow(minCapacity); -} - -/** - * Increases the capacity to ensure that it can hold at least the - * number of elements specified by the minimum capacity argument. - * - * @param minCapacity the desired minimum capacity - */ -private void grow(int minCapacity) { - // overflow-conscious code - int oldCapacity = elementData.length; - // 扩容规则是当前容量 + 当前容量右移1位。也就是1.5倍。 - int newCapacity = oldCapacity + (oldCapacity >> 1); - if (newCapacity - minCapacity < 0) - newCapacity = minCapacity; - // 是否大于 Int 最大值,也就是容量最大值 - if (newCapacity - MAX_ARRAY_SIZE > 0) - newCapacity = hugeCapacity(minCapacity); - // minCapacity is usually close to size, so this is a win: - // 拷贝元素到扩充后的新的 ArrayList - elementData = Arrays.copyOf(elementData, newCapacity); -} -``` - -通过源码发现扩容逻辑还是比较简单的,整理下具体的扩容流程如下: - -1. 开始检查当前插入位置时数组容量是否足够 - -2. ArrayList 是否未初始化,未初始化是则初始化 ArrayList ,容量给 10. - -3. 判断当前要插入的下标是否大于容量 - - 1. 不大于,插入新增元素,新增流程完毕。 - -4. 如果所需的容量大于当前容量,开始扩充。 - - 1. 扩容规则是当前容量 + 当前容量右移1位。也就是1.5倍。 - - `int newCapacity = oldCapacity + (oldCapacity >> 1);` - - 2. 如果扩充之后还是小于需要的最小容量,则把所需最小容量作为容量。 - - 3. 如果容量大于默认最大容量,则使用 最大值 Integer 作为容量。 - - 4. 拷贝老数组元素到扩充后的新数组 - -5. 插入新增元素,新增流程完毕。 - -### ArrayList 数据新增 - -上面分析扩容时候已经看到了新增一个元素的具体逻辑,因为底层是数组,所以直接指定下标赋值即可,非常简单。 - -```java -public boolean add(E e) { - ensureCapacityInternal(size + 1); // Increments modCount!! - elementData[size++] = e; // 直接赋值 - return true; -} -``` - -但是还有一种新增数据的情况,就是新增时指定了要加入的下标位置。这时逻辑有什么不同呢? - -```java -/** - * Inserts the specified element at the specified position in this - * list. Shifts the element currently at that position (if any) and - * any subsequent elements to the right (adds one to their indices). - * - * @param index index at which the specified element is to be inserted - * @param element element to be inserted - * @throws IndexOutOfBoundsException {@inheritDoc} - */ -public void add(int index, E element) { - rangeCheckForAdd(index); - ensureCapacityInternal(size + 1); // Increments modCount!! - // 指定下标开始所有元素后移一位 - System.arraycopy(elementData, index, elementData, index + 1,size - index); - elementData[index] = element; - size++; -} -``` - -可以发现这种新增多了关键的一行,它的作用是把从要插入的坐标开始的元素都向后移动一位,这样才能给指定下标腾出空间,才可以放入新增的元素。 - -比如你要在下标为 3 的位置新增数据100,那么下标为3开始的所有元素都需要后移一位。 - -![ArrayList 随机新增数据](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200809004018640.png) - -由此也可以看到 ArrayList 的一个缺点,**随机插入新数据时效率不高**。 - -### ArrayList 数据获取 - -数据下标获取元素值,**一步到位,不必多言**。 - -```java -public E get(int index) { - rangeCheck(index); - return elementData(index); -} -E elementData(int index) { - return (E) elementData[index]; -} -``` - -## LinkedList - -LinkedList 的底层就是一个链表线性结构了,链表除了要有一个节点对象外,根据单向链表和双向链表的不同,还有一个或者两个指针。那么 LinkedList 是单链表还是双向链表呢? - -### LinkedList 存储结构 -依旧深入 LinkedList 源码一探究竟,可以看到 LinkedList 无参构造里没有任何操作,不过我们通过查看变量 first、last 可以发现它们就是存储链表第一个和最后 一个的节点。 -```java -transient int size = 0; -/** - * Pointer to first node. - * Invariant: (first == null && last == null) || - * (first.prev == null && first.item != null) - */ -transient Node first; - -/** - * Pointer to last node. - * Invariant: (first == null && last == null) || - * (last.next == null && last.item != null) - */ -transient Node last; - -/** - * Constructs an empty list. - */ -public LinkedList() { -} -``` - -变量 first 和 last 都是 Node 类型,继而查看 Node 源码。 - -``` java -private static class Node { - E item; - Node next; - Node prev; - - Node(Node prev, E element, Node next) { - this.item = element; - this.next = next; - this.prev = prev; - } -} -``` - -可以看到这就是一个典型的**双向链表**结构,item 用来存放元素值;next 指向下一个 node 节点,prev 指向上一个 node 节点。 - -![双向链表(图片来自 appcoda.com)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200809142450661.png) - -### LinkedList 数据获取 - -链表不像数组是连续的内存地址,链表是通过next 和 prev 指向记录链接路径的,所以查找指定位置的 node 只能遍历查找,查看源码也是如此。 - -```java -public E get(int index) { - checkElementIndex(index); - return node(index).item; -} -/** - * Returns the (non-null) Node at the specified element index. - */ -// 遍历查找 index 位置的节点信息 -Node node(int index) { - // assert isElementIndex(index); - // 这里判断 index 是在当前链表的前半部分还是后半部分,然后决定是从 - // first 向后查找还是从 last 向前查找。 - if (index < (size >> 1)) { - Node x = first; - for (int i = 0; i < index; i++) - x = x.next; - return x; - } else { - Node x = last; - for (int i = size - 1; i > index; i--) - x = x.prev; - return x; - } -} -``` - -查找指定位置的 node 对象,这个部分要注意的是,查找会首先判断 index 是在当前链表的前半部分还是后半部分,然后决定是从 first 向后查找还是从 last 向前查找。这样可以增加查找速度。从这里也可以看出链表在查找指定位置元素时,效率不高。 - -### LinkedList 数据新增 - -因为 LinkedList 是链表,所以 LinkedList 的新增也就是链表的数据新增了,这时候要根据要插入的位置的区分操作。 - -1. 尾部插入 - - ```java - public boolean add(E e) { - linkLast(e); - return true; - } - void linkLast(E e) { - final Node l = last; - // 新节点,prev 为当前尾部节点,e为元素值,next 为 null, - final Node newNode = new Node<>(l, e, null); - last = newNode; - if (l == null) - first = newNode; - else - // 目前的尾部节点 next 指向新的节点 - l.next = newNode; - size++; - modCount++; - } - ``` - - 默认的 add 方式就是尾部新增了,尾部新增的逻辑很简单,只需要创建一个新的节点,新节点的 prev 设置现有的末尾节点,现有的末尾 Node 指向新节点 Node,新节点的 next 设为 null 即可。 - -2. 中间新增 - - 下面是在指定位置新增元素,涉及到的源码部分。 - - ```java - public void add(int index, E element) { - checkPositionIndex(index); - if (index == size) - // 如果位置就是当前链表尾部,直接尾插 - linkLast(element); - else - // 获取 index 位置的节点,插入新的元素 - linkBefore(element, node(index)); - } - - /** - * Inserts element e before non-null Node succ. - */ - // 在指定节点处新增元素,修改指定元素的下一个节点为新增元素,新增元素的下一个节点是查找到的 node 的next节点指向, - // 新增元素的上一个节点为查找到的 node 节点,查找到的 node 节点的 next 指向 node 的 prev 修改为新 Node - void linkBefore(E e, Node succ) { - // assert succ != null; - final Node pred = succ.prev; - final Node newNode = new Node<>(pred, e, succ); - succ.prev = newNode; - if (pred == null) - first = newNode; - else - pred.next = newNode; - size++; - modCount++; - } - - ``` - - 可以看到指定位置插入元素主要分为两个部分,第一个部分是查找 node 节点部分,这部分就是上面介绍的 LinkedList 数据获取部分, - - 第二个部分是在查找到的 node 对象后插入元素。主要就是修改 node 的 next 指向为新节点,新节点的 prev 指向为查找到的 node 节点,新节点的 next 指向为查找到的 node 节点的 next 指向。查找到的 node 节点的 next 指向的 node 节点的 prev 修改为新节点。 - - ![LinkedLst 插入元素](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200809145942634.png) - -### LinkedList 数据删除 - -依旧查看源码进行分析,源码中看到如果节点是头结点或者尾节点,删除比较简单。我们主要看删除中间一个节点时的操作 - -```java -public E remove(int index) { - checkElementIndex(index); - return unlink(node(index)); -} -/** - * Unlinks non-null node x. - */ -E unlink(Node x) { - // assert x != null; - final E element = x.item; - final Node next = x.next; - final Node prev = x.prev; - - if (prev == null) { - first = next; - } else { - prev.next = next; - x.prev = null; - } - - if (next == null) { - last = prev; - } else { - next.prev = prev; - x.next = null; - } - - x.item = null; - size--; - modCount++; - return element; -} -``` - -node(index) 方法依旧是二分查找目标位置,然后进行删除操作。比如要删除的节点叫做 X,删除操作主要是修改 X 节点的 prev 节点的 next 指向为 X 节点的 next 指向,修改 X 节点的 next 节点的 prev 指向为 X 节点的 prev 指向,最后把 X 节点的 prev 和 next 指向清空。如果理解起来有点费劲,可以看下面这个图,可能会比较明白。 - -![LinkedList 删除数据](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200810222445628.png) - -### 扩展 - -你以为 LinkedList 只是一个 List,其他它不仅实现了 List 接口,还实现了 Deque ,所以它表面上是一个 List,其实它还是一个队列。 - -```java -public class LinkedList extends AbstractSequentialList - implements List, Deque, Cloneable, java.io.Serializable -``` -体验一下先进先出的队列。 -```java -Queue queue = new LinkedList<>(); -queue.add("a"); -queue.add("b"); -queue.add("c"); -queue.add("d"); -System.out.println(queue.poll()); -System.out.println(queue.poll()); -System.out.println(queue.poll()); -System.out.println(queue.poll()); -// result: -// a -// b -// c -// d -``` -同学可以思考一下这个队列是怎么实现的,其实很简单对不对,就是先进先出嘛,`poll` 时删除 first 节点不就完事了嘛。 - -## 总结 - -不管是 ArrayList 还是 LinkedList 都是开发中常用的集合类,这篇文章分析了两者的底层实现,通过对底层实现的分析我们可以总结出两者的主要优缺点。 - -1. 遍历,ArrayList 每次都是**直接定位**,LinkedList 通过 **next 节点定位**,不相上下。这里要注意的是 LinkedList 只有使用**迭代器**的方式遍历才会使用 next 节点。如果使用 `get` ,则因为遍历查找效率低下。 -2. 新增,ArrayList 可能会需要**扩容**,中间插入时,ArrayList 需要**后移**插入位置之后的所有元素。LinkedList **直接修改** node 的 prev, next 指向,LinkedList 胜出。 -3. 删除,同 2. -4. **随机访问**指定位置,ArrayList 直接定位,LinkedList 从头会尾开始查找,**数组胜出**。 - -综上所述,ArrayList 适合存储和访问数据,LinkedList 则更适合数据的处理,希望你以后在使用时可以合理的选择 List 结构。 - - - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-src-concurrent-hashmap.md b/docs/jdk/java-src-concurrent-hashmap.md deleted file mode 100644 index e35d31c..0000000 --- a/docs/jdk/java-src-concurrent-hashmap.md +++ /dev/null @@ -1,603 +0,0 @@ ---- -title: 还不懂 ConcurrentHashMap ?这份源码分析了解一下 -date: 2020-04-07 08:01:01 -url: jdk/concurrent-hashmap -tags: - - HashMap - - ConcruuentHashMap -categories: - - Java 源码分析 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -上一篇文章介绍了 HashMap 源码,反响不错,也有很多同学发表了自己的观点,这次又来了,这次是 `ConcurrentHashMap ` 了,作为线程安全的HashMap ,它的使用频率也是很高。那么它的存储结构和实现原理是怎么样的呢? -## 1. ConcurrentHashMap 1.7 -### 1. 存储结构 - -![Java 7 ConcurrentHashMap 存储结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200405151029416.png) - -Java 7 中 ConcurrentHashMap 的存储结构如上图,ConcurrnetHashMap 由很多个 Segment 组合,而每一个 Segment 是一个类似于 HashMap 的结构,所以每一个 HashMap 的内部可以进行扩容。但是 Segment 的个数一旦**初始化就不能改变**,默认 Segment 的个数是 16 个,你也可以认为 ConcurrentHashMap 默认支持最多 16 个线程并发。 - -### 2. 初始化 - -通过 ConcurrentHashMap 的无参构造探寻 ConcurrentHashMap 的初始化流程。 - -```java - /** - * Creates a new, empty map with a default initial capacity (16), - * load factor (0.75) and concurrencyLevel (16). - */ - public ConcurrentHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL); - } -``` - -无参构造中调用了有参构造,传入了三个参数的默认值,他们的值是。 - -```java - /** - * 默认初始化容量 - */ - static final int DEFAULT_INITIAL_CAPACITY = 16; - - /** - * 默认负载因子 - */ - static final float DEFAULT_LOAD_FACTOR = 0.75f; - - /** - * 默认并发级别 - */ - static final int DEFAULT_CONCURRENCY_LEVEL = 16; -``` - -接着看下这个有参构造函数的内部实现逻辑。 - -```java -@SuppressWarnings("unchecked") -public ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel) { - // 参数校验 - if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) - throw new IllegalArgumentException(); - // 校验并发级别大小,大于 1<<16,重置为 65536 - if (concurrencyLevel > MAX_SEGMENTS) - concurrencyLevel = MAX_SEGMENTS; - // Find power-of-two sizes best matching arguments - // 2的多少次方 - int sshift = 0; - int ssize = 1; - // 这个循环可以找到 concurrencyLevel 之上最近的 2的次方值 - while (ssize < concurrencyLevel) { - ++sshift; - ssize <<= 1; - } - // 记录段偏移量 - this.segmentShift = 32 - sshift; - // 记录段掩码 - this.segmentMask = ssize - 1; - // 设置容量 - if (initialCapacity > MAXIMUM_CAPACITY) - initialCapacity = MAXIMUM_CAPACITY; - // c = 容量 / ssize ,默认 16 / 16 = 1,这里是计算每个 Segment 中的类似于 HashMap 的容量 - int c = initialCapacity / ssize; - if (c * ssize < initialCapacity) - ++c; - int cap = MIN_SEGMENT_TABLE_CAPACITY; - //Segment 中的类似于 HashMap 的容量至少是2或者2的倍数 - while (cap < c) - cap <<= 1; - // create segments and segments[0] - // 创建 Segment 数组,设置 segments[0] - Segment s0 = new Segment(loadFactor, (int)(cap * loadFactor), - (HashEntry[])new HashEntry[cap]); - Segment[] ss = (Segment[])new Segment[ssize]; - UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] - this.segments = ss; -} -``` - -总结一下在 Java 7 中 ConcurrnetHashMap 的初始化逻辑。 - -1. 必要参数校验。 -2. 校验并发级别 concurrencyLevel 大小,如果大于最大值,重置为最大值。无惨构造**默认值是 16.** -3. 寻找并发级别 concurrencyLevel 之上最近的 **2 的幂次方**值,作为初始化容量大小,**默认是 16**。 -4. 记录 segmentShift 偏移量,这个值为【容量 = 2 的N次方】中的 N,在后面 Put 时计算位置时会用到。**默认是 32 - sshift = 28**. -5. 记录 segmentMask,默认是 ssize - 1 = 16 -1 = 15. -6. **初始化 segments[0]**,**默认大小为 2**,**负载因子 0.75**,**扩容阀值是 2*0.75=1.5**,插入第二个值时才会进行扩容。 - -### 3. put - -接着上面的初始化参数继续查看 put 方法源码。 - -```java -/** - * Maps the specified key to the specified value in this table. - * Neither the key nor the value can be null. - * - *

The value can be retrieved by calling the get method - * with a key that is equal to the original key. - * - * @param key key with which the specified value is to be associated - * @param value value to be associated with the specified key - * @return the previous value associated with key, or - * null if there was no mapping for key - * @throws NullPointerException if the specified key or value is null - */ -public V put(K key, V value) { - Segment s; - if (value == null) - throw new NullPointerException(); - int hash = hash(key); - // hash 值无符号右移 28位(初始化时获得),然后与 segmentMask=15 做与运算 - // 其实也就是把高4位与segmentMask(1111)做与运算 - int j = (hash >>> segmentShift) & segmentMask; - if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck - (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment - // 如果查找到的 Segment 为空,初始化 - s = ensureSegment(j); - return s.put(key, hash, value, false); -} - -/** - * Returns the segment for the given index, creating it and - * recording in segment table (via CAS) if not already present. - * - * @param k the index - * @return the segment - */ -@SuppressWarnings("unchecked") -private Segment ensureSegment(int k) { - final Segment[] ss = this.segments; - long u = (k << SSHIFT) + SBASE; // raw offset - Segment seg; - // 判断 u 位置的 Segment 是否为null - if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) { - Segment proto = ss[0]; // use segment 0 as prototype - // 获取0号 segment 里的 HashEntry 初始化长度 - int cap = proto.table.length; - // 获取0号 segment 里的 hash 表里的扩容负载因子,所有的 segment 的 loadFactor 是相同的 - float lf = proto.loadFactor; - // 计算扩容阀值 - int threshold = (int)(cap * lf); - // 创建一个 cap 容量的 HashEntry 数组 - HashEntry[] tab = (HashEntry[])new HashEntry[cap]; - if ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) == null) { // recheck - // 再次检查 u 位置的 Segment 是否为null,因为这时可能有其他线程进行了操作 - Segment s = new Segment(lf, threshold, tab); - // 自旋检查 u 位置的 Segment 是否为null - while ((seg = (Segment)UNSAFE.getObjectVolatile(ss, u)) - == null) { - // 使用CAS 赋值,只会成功一次 - if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s)) - break; - } - } - } - return seg; -} -``` - -上面的源码分析了 ConcurrentHashMap 在 put 一个数据时的处理流程,下面梳理下具体流程。 - -1. 计算要 put 的 key 的位置,获取指定位置的 Segment。 - -2. 如果指定位置的 Segment 为空,则初始化这个 Segment. - - **初始化 Segment 流程:** - - 1. 检查计算得到的位置的 Segment 是否为null. - 2. 为 null 继续初始化,使用 Segment[0] 的容量和负载因子创建一个 HashEntry 数组。 - 3. 再次检查计算得到的指定位置的 Segment 是否为null. - 4. 使用创建的 HashEntry 数组初始化这个 Segment. - 5. 自旋判断计算得到的指定位置的 Segment 是否为null,使用 CAS 在这个位置赋值为 Segment. - -3. Segment.put 插入 key,value 值。 - -上面探究了获取 Segment 段和初始化 Segment 段的操作。最后一行的 Segment 的 put 方法还没有查看,继续分析。 - -```java -final V put(K key, int hash, V value, boolean onlyIfAbsent) { - // 获取 ReentrantLock 独占锁,获取不到,scanAndLockForPut 获取。 - HashEntry node = tryLock() ? null : scanAndLockForPut(key, hash, value); - V oldValue; - try { - HashEntry[] tab = table; - // 计算要put的数据位置 - int index = (tab.length - 1) & hash; - // CAS 获取 index 坐标的值 - HashEntry first = entryAt(tab, index); - for (HashEntry e = first;;) { - if (e != null) { - // 检查是否 key 已经存在,如果存在,则遍历链表寻找位置,找到后替换 value - K k; - if ((k = e.key) == key || - (e.hash == hash && key.equals(k))) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; - ++modCount; - } - break; - } - e = e.next; - } - else { - // first 有值没说明 index 位置已经有值了,有冲突,链表头插法。 - if (node != null) - node.setNext(first); - else - node = new HashEntry(hash, key, value, first); - int c = count + 1; - // 容量大于扩容阀值,小于最大容量,进行扩容 - if (c > threshold && tab.length < MAXIMUM_CAPACITY) - rehash(node); - else - // index 位置赋值 node,node 可能是一个元素,也可能是一个链表的表头 - setEntryAt(tab, index, node); - ++modCount; - count = c; - oldValue = null; - break; - } - } - } finally { - unlock(); - } - return oldValue; -} -``` - -由于 Segment 继承了 ReentrantLock,所以 Segment 内部可以很方便的获取锁,put 流程就用到了这个功能。 - -1. tryLock() 获取锁,获取不到使用 **`scanAndLockForPut`** 方法继续获取。 - -2. 计算 put 的数据要放入的 index 位置,然后获取这个位置上的 HashEntry 。 - -3. 遍历 put 新元素,为什么要遍历?因为这里获取的 HashEntry 可能是一个空元素,也可能是链表已存在,所以要区别对待。 - - 如果这个位置上的 **HashEntry 不存在**: - - 1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。 - 2. 直接头插法插入。 - - 如果这个位置上的 **HashEntry 存在**: - - 1. 判断链表当前元素 Key 和 hash 值是否和要 put 的 key 和 hash 值一致。一致则替换值 - 2. 不一致,获取链表下一个节点,直到发现相同进行值替换,或者链表表里完毕没有相同的。 - 1. 如果当前容量大于扩容阀值,小于最大容量,**进行扩容**。 - 2. 直接链表头插法插入。 - -4. 如果要插入的位置之前已经存在,替换后返回旧值,否则返回 null. - -这里面的第一步中的 scanAndLockForPut 操作这里没有介绍,这个方法做的操作就是不断的自旋 `tryLock()` 获取锁。当自旋次数大于指定次数时,使用 `lock()` 阻塞获取锁。在自旋时顺表获取下 hash 位置的 HashEntry。 - -```java -private HashEntry scanAndLockForPut(K key, int hash, V value) { - HashEntry first = entryForHash(this, hash); - HashEntry e = first; - HashEntry node = null; - int retries = -1; // negative while locating node - // 自旋获取锁 - while (!tryLock()) { - HashEntry f; // to recheck first below - if (retries < 0) { - if (e == null) { - if (node == null) // speculatively create node - node = new HashEntry(hash, key, value, null); - retries = 0; - } - else if (key.equals(e.key)) - retries = 0; - else - e = e.next; - } - else if (++retries > MAX_SCAN_RETRIES) { - // 自旋达到指定次数后,阻塞等到只到获取到锁 - lock(); - break; - } - else if ((retries & 1) == 0 && - (f = entryForHash(this, hash)) != first) { - e = first = f; // re-traverse if entry changed - retries = -1; - } - } - return node; -} - -``` - -### 4. 扩容 rehash - -ConcurrentHashMap 的扩容只会扩容到原来的两倍。老数组里的数据移动到新的数组时,位置要么不变,要么变为 index+ oldSize,参数里的 node 会在扩容之后使用链表**头插法**插入到指定位置。 - -```java -private void rehash(HashEntry node) { - HashEntry[] oldTable = table; - // 老容量 - int oldCapacity = oldTable.length; - // 新容量,扩大两倍 - int newCapacity = oldCapacity << 1; - // 新的扩容阀值 - threshold = (int)(newCapacity * loadFactor); - // 创建新的数组 - HashEntry[] newTable = (HashEntry[]) new HashEntry[newCapacity]; - // 新的掩码,默认2扩容后是4,-1是3,二进制就是11。 - int sizeMask = newCapacity - 1; - for (int i = 0; i < oldCapacity ; i++) { - // 遍历老数组 - HashEntry e = oldTable[i]; - if (e != null) { - HashEntry next = e.next; - // 计算新的位置,新的位置只可能是不便或者是老的位置+老的容量。 - int idx = e.hash & sizeMask; - if (next == null) // Single node on list - // 如果当前位置还不是链表,只是一个元素,直接赋值 - newTable[idx] = e; - else { // Reuse consecutive sequence at same slot - // 如果是链表了 - HashEntry lastRun = e; - int lastIdx = idx; - // 新的位置只可能是不便或者是老的位置+老的容量。 - // 遍历结束后,lastRun 后面的元素位置都是相同的 - for (HashEntry last = next; last != null; last = last.next) { - int k = last.hash & sizeMask; - if (k != lastIdx) { - lastIdx = k; - lastRun = last; - } - } - // ,lastRun 后面的元素位置都是相同的,直接作为链表赋值到新位置。 - newTable[lastIdx] = lastRun; - // Clone remaining nodes - for (HashEntry p = e; p != lastRun; p = p.next) { - // 遍历剩余元素,头插法到指定 k 位置。 - V v = p.value; - int h = p.hash; - int k = h & sizeMask; - HashEntry n = newTable[k]; - newTable[k] = new HashEntry(h, p.key, v, n); - } - } - } - } - // 头插法插入新的节点 - int nodeIndex = node.hash & sizeMask; // add the new node - node.setNext(newTable[nodeIndex]); - newTable[nodeIndex] = node; - table = newTable; -} -``` - -有些同学可能会对最后的两个 for 循环有疑惑,这里第一个 for 是为了寻找这样一个节点,这个节点后面的所有 next 节点的新位置都是相同的。然后把这个作为一个链表赋值到新位置。第二个 for 循环是为了把剩余的元素通过头插法插入到指定位置链表。这样实现的原因可能是基于概率统计,有深入研究的同学可以发表下意见。 - -### 5. get - -到这里就很简单了,get 方法只需要两步即可。 - -1. 计算得到 key 的存放位置。 -2. 遍历指定位置查找相同 key 的 value 值。 - -```java -public V get(Object key) { - Segment s; // manually integrate access methods to reduce overhead - HashEntry[] tab; - int h = hash(key); - long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE; - // 计算得到 key 的存放位置 - if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null && - (tab = s.table) != null) { - for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile - (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE); - e != null; e = e.next) { - // 如果是链表,遍历查找到相同 key 的 value。 - K k; - if ((k = e.key) == key || (e.hash == h && key.equals(k))) - return e.value; - } - } - return null; -} -``` - -## 2. ConcurrentHashMap 1.8 - -### 1. 存储结构 - -![Java8 ConcurrentHashMap 存储结构(图片来自 javadoop)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/java8_concurrenthashmap.png) - -可以发现 Java8 的 ConcurrentHashMap 相对于 Java7 来说变化比较大,不再是之前的 **Segment 数组 + HashEntry 数组 + 链表**,而是 **Node 数组 + 链表 / 红黑树**。当冲突链表达到一定长度时,链表会转换成红黑树。 - -### 2. 初始化 initTable - -```java -/** - * Initializes table, using the size recorded in sizeCtl. - */ -private final Node[] initTable() { - Node[] tab; int sc; - while ((tab = table) == null || tab.length == 0) { - // 如果 sizeCtl < 0 ,说明另外的线程执行CAS 成功,正在进行初始化。 - if ((sc = sizeCtl) < 0) - // 让出 CPU 使用权 - Thread.yield(); // lost initialization race; just spin - else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { - try { - if ((tab = table) == null || tab.length == 0) { - int n = (sc > 0) ? sc : DEFAULT_CAPACITY; - @SuppressWarnings("unchecked") - Node[] nt = (Node[])new Node[n]; - table = tab = nt; - sc = n - (n >>> 2); - } - } finally { - sizeCtl = sc; - } - break; - } - } - return tab; -} -``` - -从源码中可以发现 ConcurrentHashMap 的初始化是通过**自旋和 CAS** 操作完成的。里面需要注意的是变量 `sizeCtl` ,它的值决定着当前的初始化状态。 - -1. -1 说明正在初始化 -2. -N 说明有N-1个线程正在进行扩容 -3. 表示 table 初始化大小,如果 table 没有初始化 -4. 表示 table 容量,如果 table 已经初始化。 - -### 3. put - -直接过一遍 put 源码。 - -```java -public V put(K key, V value) { - return putVal(key, value, false); -} - -/** Implementation for put and putIfAbsent */ -final V putVal(K key, V value, boolean onlyIfAbsent) { - // key 和 value 不能为空 - if (key == null || value == null) throw new NullPointerException(); - int hash = spread(key.hashCode()); - int binCount = 0; - for (Node[] tab = table;;) { - // f = 目标位置元素 - Node f; int n, i, fh;// fh 后面存放目标位置的元素 hash 值 - if (tab == null || (n = tab.length) == 0) - // 数组桶为空,初始化数组桶(自旋+CAS) - tab = initTable(); - else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { - // 桶内为空,CAS 放入,不加锁,成功了就直接 break 跳出 - if (casTabAt(tab, i, null,new Node(hash, key, value, null))) - break; // no lock when adding to empty bin - } - else if ((fh = f.hash) == MOVED) - tab = helpTransfer(tab, f); - else { - V oldVal = null; - // 使用 synchronized 加锁加入节点 - synchronized (f) { - if (tabAt(tab, i) == f) { - // 说明是链表 - if (fh >= 0) { - binCount = 1; - // 循环加入新的或者覆盖节点 - for (Node e = f;; ++binCount) { - K ek; - if (e.hash == hash && - ((ek = e.key) == key || - (ek != null && key.equals(ek)))) { - oldVal = e.val; - if (!onlyIfAbsent) - e.val = value; - break; - } - Node pred = e; - if ((e = e.next) == null) { - pred.next = new Node(hash, key, - value, null); - break; - } - } - } - else if (f instanceof TreeBin) { - // 红黑树 - Node p; - binCount = 2; - if ((p = ((TreeBin)f).putTreeVal(hash, key, - value)) != null) { - oldVal = p.val; - if (!onlyIfAbsent) - p.val = value; - } - } - } - } - if (binCount != 0) { - if (binCount >= TREEIFY_THRESHOLD) - treeifyBin(tab, i); - if (oldVal != null) - return oldVal; - break; - } - } - } - addCount(1L, binCount); - return null; -} -``` - -1. 根据 key 计算出 hashcode 。 - -2. 判断是否需要进行初始化。 - -3. 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。 - -4. 如果当前位置的 `hashcode == MOVED == -1`,则需要进行扩容。 - -5. 如果都不满足,则利用 synchronized 锁写入数据。 - -6. 如果数量大于 `TREEIFY_THRESHOLD` 则要转换为红黑树。 - -### 4. get - -get 流程比较简单,直接过一遍源码。 - -```java -public V get(Object key) { - Node[] tab; Node e, p; int n, eh; K ek; - // key 所在的 hash 位置 - int h = spread(key.hashCode()); - if ((tab = table) != null && (n = tab.length) > 0 && - (e = tabAt(tab, (n - 1) & h)) != null) { - // 如果指定位置元素存在,头结点hash值相同 - if ((eh = e.hash) == h) { - if ((ek = e.key) == key || (ek != null && key.equals(ek))) - // key hash 值相等,key值相同,直接返回元素 value - return e.val; - } - else if (eh < 0) - // 头结点hash值小于0,说明正在扩容或者是红黑树,find查找 - return (p = e.find(h, key)) != null ? p.val : null; - while ((e = e.next) != null) { - // 是链表,遍历查找 - if (e.hash == h && - ((ek = e.key) == key || (ek != null && key.equals(ek)))) - return e.val; - } - } - return null; -} -``` - -总结一下 get 过程: - -1. 根据 hash 值计算位置。 -2. 查找到指定位置,如果头节点就是要找的,直接返回它的 value. -3. 如果头节点 hash 值小于 0 ,说明正在扩容或者是红黑树,查找之。 -4. 如果是链表,遍历查找之。 - -总结: - -总的来说 ConcruuentHashMap 在 Java8 中相对于 Java7 来说变化还是挺大的, - -## 3. 总结 - -Java7 中 ConcruuentHashMap 使用的分段锁,也就是每一个 Segment 上同时只有一个线程可以操作,每一个 Segment 都是一个类似 HashMap 数组的结构,它可以扩容,它的冲突会转化为链表。但是 Segment 的个数一旦初始化就不能改变。 - -Java8 中的 ConcruuentHashMap 使用的 Synchronized 锁加 CAS 的机制。结构也由 Java7 中的 **Segment 数组 + HashEntry 数组 + 链表** 进化成了 **Node 数组 + 链表 / 红黑树**,Node 是类似于一个 HashEntry 的结构。它的冲突再达到一定大小时会转化成红黑树,在冲突小于一定数量时又退回链表。 - -有些同学可能对 Synchronized 的性能存在疑问,其实 Synchronized 锁自从引入锁升级策略后,性能不再是问题,有兴趣的同学可以自己了解下 Synchronized 的**锁升级**。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jdk/java-src-hashmap.md b/docs/jdk/java-src-hashmap.md deleted file mode 100644 index 6946882..0000000 --- a/docs/jdk/java-src-hashmap.md +++ /dev/null @@ -1,488 +0,0 @@ ---- -title: 最通俗易懂的 HashMap 源码分析解读 -date: 2020-03-31 08:01:01 -url: jdk/hashmap -tags: - - HashMap -categories: - - Java 源码分析 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -HashMap 作为最常用的集合类之一,有必要深入浅出的了解一下。这篇文章会深入到 HashMap 源码,剖析它的存储结构以及工作机制。 - -### 1. HashMap 的存储结构 - -HashMap 的数据存储结构是一个 Node 数组,在(Java 7 中是 Entry 数组,但结构相同) - -```java -public class HashMap extends AbstractMap implements Map, Cloneable, Serializable { - // 数组 - transient Node[] table; - static class Node implements Map.Entry { - final int hash; - final K key; - V value; - // 链表 - Node next; - .... - } - ..... -} -``` - -存储结构主要是****数组加链表****,像下面的图。 - -![HashMap 存储结构(图片来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/hashmap.png) - - - -### 2. HashMap 的 put() - - 在 Java 8 中 HashMap 的 put 方法如下,我已经详细注释了重要代码。 - -```java -public V put(K key, V value) { - return putVal(hash(key), key, value, false, true); -} -// 计算哈希值 与(&)、非(~)、或(|)、异或(^) -static final int hash(Object key) { - int h; - return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); -} -final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { - - Node[] tab; Node p; int n, i; - // 如果数组为空,进行 resize() 初始化 - if ((tab = table) == null || (n = tab.length) == 0) - n = (tab = resize()).length; - // 如果计算的位置上Node不存在,直接创建节点插入 - if ((p = tab[i = (n - 1) & hash]) == null) - tab[i] = newNode(hash, key, value, null); - else { - // 如果计算的位置上Node 存在,链表处理 - Node e; K k; - // 如果 hash 值,k 值完全相同,直接覆盖 - if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) - e = p; - // 如果 index 位置元素已经存在,且是红黑树 - else if (p instanceof TreeNode) - e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); - else { - // 如果这次要放入的值不存在 - for (int binCount = 0; ; ++binCount) { - // 尾插法 - if ((e = p.next) == null) { - // 找到节点链表中next为空的节点,创建新的节点插入 - p.next = newNode(hash, key, value, null); - // 如果节点链表中数量超过TREEIFY_THRESHOLD(8)个,转化为红黑树 - if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st - treeifyBin(tab, hash); - break; - } - // 如果节点链表中有发现已有相同key - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - break; - p = e; - } - } - // 如果节点 e 有值,放入数组 table[] - if (e != null) { // existing mapping for key - V oldValue = e.value; - if (!onlyIfAbsent || oldValue == null) - e.value = value; - afterNodeAccess(e); - return oldValue; - } - } - - ++modCount; - // 当前大小大于临界大小,扩容 - if (++size > threshold) - resize(); - afterNodeInsertion(evict); - return null; -} -``` - -举个例子,如果 put 的 key 为字母 a,当前 HashMap 容量是初始容量 16,计算出位置是 1。 - -```java -# int hash = key.hashCode() -# hash = hash ^ (hash >>> 16) -# 公式 index = (n - 1) & hash // n 是容量 - -hash HEX(97) = 0110 0001‬ -n-1 HEX(15) = 0000 1111 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -``` - -总结 HashMap put 过程。 - -1. 计算 key 的 hash 值。 - - 计算方式是 ` (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);` - -2. 检查当前数组是否为空,为空需要进行初始化,初始化容量是 **16** ,负载因子默认 **0.75**。 - -3. 计算 key 在数组中的坐标。 - - 计算方式:`(容量 - 1) & hash`. - - 因为容量总是2的次方,所以-1的值的二进制**总是全1**。方便与 hash 值进行**与**运算。 - -4. 如果计算出的坐标元素为空,创建节点加入,put 结束。 - - 1. 如果当前数组容量大于负载因子设置的容量,**进行扩容**。 - -5. 如果计算出的坐标元素有值。 - - 1. 如果坐标上的元素值和要加入的值 key 完全一样,覆盖原有值。 - - 2. 如果坐标上的元素是**红黑树**,把要加入的值和 key 加入到红黑树。 - - 3. 如果坐标上的元素和要加入的元素不同(**尾插法**增加)。 - - 1. 如果 next 节点为空,把要加入的值和 key 加入 next 节点。 - - 2. 如果 next 节点不为空,循环查看 next 节点。 - - 如果发现有 next 节点的 key 和要加入的 key 一样,对应的值替换为新值。 - - 3. 如果循环 next 节点查找**超过8层**还不为空,把这个位置元素转换为**红黑树**。 - -### 3. HashMap 的 get() - -在 Java 8 中 get 方法源码如下,我已经做了注释说明。 - -```java -public V get(Object key) { - Node e; - return (e = getNode(hash(key), key)) == null ? null : e.value; -} -final Node getNode(int hash, Object key) { - Node[] tab; Node first, e; int n; K k; - // 只有在存储数组已经存在的情况下进入这个 if - if ((tab = table) != null && (n = tab.length) > 0 && - (first = tab[(n - 1) & hash]) != null) { - // first 是获取的坐标上元素 - if (first.hash == hash && // always check first node - ((k = first.key) == key || (key != null && key.equals(k)))) - // key 相同,说明first是想要的元素,返回 - return first; - if ((e = first.next) != null) { - if (first instanceof TreeNode) - // 如果是红黑树,从红黑树中查找结果 - return ((TreeNode)first).getTreeNode(hash, key); - do { - // 循环遍历查找 - if (e.hash == hash && - ((k = e.key) == key || (key != null && key.equals(k)))) - return e; - } while ((e = e.next) != null); - } - } - return null; - } -``` - -get 方法流程总结。 - -1. 计算 key 的 hash 值。 -2. 如果存储数组不为空,且计算得到的位置上的元素不为空。继续,否则,返回 Null。 -3. 如果获取到的元素的 key 值相等,说明查找到了,返回元素。 -4. 如果获取到的元素的 key 值不相等,查找 next 节点的元素。 - 1. 如果元素是红黑树,在红黑树中查找。 - 2. 不是红黑树,遍历 next 节点查找,找到则返回。 - -### 4. HashMap 的 Hash 规则 - -1. 计算 hash 值 int hash = key.hashCode()。 -2. **与或**上 hash 值无符号右移16 位。 hash = hash ^ (hash >>> 16)。 -3. 位置计算公式 ` index = (n - 1) & hash` ,其中 `n` 是容量。 - -这里可能会有同学对 ` hash ^ (hash >>> 16)` 有疑惑,很好奇为什么这里要拿 hash 值异或上 hash 值无符号右移 16 位呢?下面通过一个例子演示其中道理所在。 - -假设 hash 值是 `0001 0100 1100 0010 0110 0001‬ 0010 0000`,当前容量是 16。 - -```shell -hash = 0001 0100 1100 0010 0110 0001‬ 0010 0000 --- - | 与或计算 -hash >>> 16 = 0000 0000 0000 0000 0001 0100 1100 0010 --- ------------------------------------------------------- -hash 结果 = 0001 0100 1100 0010 0111 0101 1110 0100 --- - | & 与运算 -容量 -1 = 0000 0000 0000 0000 0000 0000 0000 1111 --- ------------------------------------------------------- -# 得到位置 = 0000 0000 0000 0000 0000 0000 0000 0100 得到位置是 4 -``` - -如果又新增一个数据,得到 hash 值是 `0100 0000 1110 0010 1010 0010‬ 0001 0000` ,容量还是16,计算它的位置应该是什么呢? - -```java -hash = 0100 0000 1110 0010 1010 0010‬ 0001 0000 --- - | 与或计算 -hash >>> 16 = 0000 0000 0000 0000 0001 0100 1100 0010 --- ------------------------------------------------------- -hash 结果 = 0100 0000 1110 0010 1011 0110 1101 0010 --- - | & 与运算 -容量 -1 = 0000 0000 0000 0000 0000 0000 0000 1111 --- ------------------------------------------------------- -# 得到位置 = 0000 0000 0000 0000 0000 0000 0000 0010 得到位置是 2 -``` - -上面两个例子,得到位置一个是 4,一个是 2,上面只是我随便输入的两个二进制数,那么这两个数如果不经过 `hash ^ (hash >>> 16)` 运算,位置会有什么变化呢? - -```shell -hash = 0001 0100 1100 0010 0110 0001‬ 0010 0000 -容量 -1 = 0000 0000 0000 0000 0000 0000 0000 1111 ------------------------------------------------------- - 结果 = 0000 0000 0000 0000 0000 0000 0000 0000 -# 得到位置是 0 -hash = 0100 0000 1110 0010 1010 0010‬ 0001 0000 -容量 -1 = 0000 0000 0000 0000 0000 0000 0000 1111 ------------------------------------------------------- - 结果 = 0000 0000 0000 0000 0000 0000 0000 0000 -# 得到位置是 0 -``` - -可以发现位置都是 0 ,冲突概率提高了。可见 `hash ^ (hash >>> 16)` 让数据的 hash 值的高 16 位与低 16 位进行与或混合,可以减少低位相同时数据插入冲突的概率。 - -### 5. HashMap 的初始化大小 - -1. 初始化大小是 16,为什么是 16 呢? - - 这可能是因为每次扩容都是 2 倍。而选择 2 的次方值 16 作为初始容量,有利于扩容时重新 Hash 计算位置。为什么是 16 我想是一个经验值,理论上说只要是 2 的次方都没有问题。 - - -### 6. HashMap 的扩容方式 - -负载因子是多少?负载因子是 **0.75**。 - -扩容方式是什么?看源码说明。 - -```java - /** - * Initializes or doubles table size. If null, allocates in - * accord with initial capacity target held in field threshold. - * Otherwise, because we are using power-of-two expansion, the - * elements from each bin must either stay at same index, or move - * with a power of two offset in the new table. - * - * @return the table - */ - final Node[] resize() { - Node[] oldTab = table; - // 现有容量 - int oldCap = (oldTab == null) ? 0 : oldTab.length; - // 现有扩容阀值 - int oldThr = threshold; - int newCap, newThr = 0; - if (oldCap > 0) { - // 如果当前长度已经大于最大容量。结束扩容 - if (oldCap >= MAXIMUM_CAPACITY) { - threshold = Integer.MAX_VALUE; - return oldTab; - } - // 如果扩大两倍之后小于最大容量,且现有容量大于等于初始容量,就扩大两倍 - else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) - // 扩容阀值扩大为两倍 - newThr = oldThr << 1; // double threshold - } - // 当前容量 = 0 ,但是当前记录容量 > 0 ,获取当前记录容量。 - else if (oldThr > 0) // initial capacity was placed in threshold - // 进入这里,说明是通过指定容量和负载因子的构造函数 - newCap = oldThr; - else { // zero initial threshold signifies using defaults - // 进入这里说明是通过无参构造 - // 新的容量 - newCap = DEFAULT_INITIAL_CAPACITY; - // 新的阀值 - newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); - } - if (newThr == 0) { - float ft = (float)newCap * loadFactor; - // 计算扩容阀值 - newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? - (int)ft : Integer.MAX_VALUE); - } - threshold = newThr; - @SuppressWarnings({"rawtypes","unchecked"}) - Node[] newTab = (Node[])new Node[newCap]; - table = newTab; - // 如果 oldTab != null,说明是扩容,否则是初始化,直接返回 - if (oldTab != null) { - for (int j = 0; j < oldCap; ++j) { - Node e; - if ((e = oldTab[j]) != null) { - oldTab[j] = null; - // 如果当前元素 next节点没有元素,当前元素重新计算位置直接放入 - if (e.next == null) - newTab[e.hash & (newCap - 1)] = e; - else if (e instanceof TreeNode) - // 如果当前节点是红黑树 - ((TreeNode)e).split(this, newTab, j, oldCap); - else { // preserve order - Node loHead = null, loTail = null; - Node hiHead = null, hiTail = null; - Node next; - do { - next = e.next; - // == 0 ,位置不变 - if ((e.hash & oldCap) == 0) { - if (loTail == null) - loHead = e; - else - loTail.next = e; - loTail = e; - } - // e.hash & oldCap != 0 ,位置变为:位置+扩容前容量 - else { - if (hiTail == null) - hiHead = e; - else - hiTail.next = e; - hiTail = e; - } - } while ((e = next) != null); - if (loTail != null) { - loTail.next = null; - newTab[j] = loHead; - } - if (hiTail != null) { - hiTail.next = null; - newTab[j + oldCap] = hiHead; - } - } - } - } - } - return newTab; - } -``` - -扩容时候怎么重新确定元素在数组中的位置,我们看到是由 ` if ((e.hash & oldCap) == 0) ` 确定的。 - -```shell -hash HEX(97) = 0110 0001‬ -n HEX(16) = 0001 0000 --------------------------- - 结果 = 0000 0000 -# e.hash & oldCap = 0 计算得到位置还是扩容前位置 - -hash HEX(17) = 0001 0001‬ -n HEX(16) = 0001 0000 --------------------------- - 结果 = 0001 0000 -# e.hash & oldCap != 0 计算得到位置是扩容前位置+扩容前容量 -``` - -通过上面的分析也可以看出,只有在每次容量都是2的次方的情况下才能使用 ` if ((e.hash & oldCap) == 0) ` 判断扩容后的位置。 - -### 7. HashMap 中的红黑树 - -HashMap 在 Java 8 中的实现增加了红黑树,当链表节点达到 8 个的时候,会把链表转换成红黑树,低于 6 个的时候,会退回链表。究其原因是因为当节点过多时,使用红黑树可以更高效的查找到节点。毕竟红黑树是一种二叉查找树。 - -1. 节点个数是多少的时候,链表会转变成红黑树。 - - 链表节点个数**大于等于 8 时**,链表会转换成树结构。 - -2. 节点个数是多少的时候,红黑树会退回链表。 - - 节点个数**小于等于 6 时**,树会转变成链表。 - -3. 为什么转变条件 8 和 6 有一个差值。 - - 如果没有差值,都是 8 ,那么如果频繁的插入删除元素,链表个数又刚好在 8 徘徊,那么就会频繁的发生链表转树,树转链表。 - -### 8. 为啥容量都是2的幂? - -容量是2的幂时,key 的 hash 值然后 `& (容量-1)` 确定位置时**碰撞概率会比较低**,因为容量为 2 的幂时,减 1 之后的二进制数为全1,这样与运算的结果就等于 hash值后面与 1 进行与运算的几位。 - -下面是个例子。 - -```shell -hash HEX(97) = 0110 0001‬ -n-1 HEX(15) = 0000 1111 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -hash HEX(99) = 0110 0011‬ -n-1 HEX(15) = 0000 1111 --------------------------- - 结果 = 0000 0011 -# 计算得到位置是 3 -hash HEX(101) = 0110 0101‬ -n-1 HEX(15) = 0000 1111 --------------------------- - 结果 = 0000 0101 -# 计算得到位置是 5 -``` - -如果是其他的容量值,假设是9,进行与运算结果碰撞的概率就比较大。 - -```shell -hash HEX(97) = 0110 0001‬ -n-1 HEX(09) = 0000 1001 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -hash HEX(99) = 0110 0011‬ -n-1 HEX(09) = 0000 1001 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -hash HEX(101) = 0110 0101‬ -n-1 HEX(09) = 0000 1001 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -``` - -另外,每次都是 2 的幂也可以让 HashMap 扩容时可以方便的**重新计算位置**。 - -```shell -hash HEX(97) = 0110 0001‬ -n-1 HEX(15) = 0000 1111 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 - -hash HEX(97) = 0110 0001‬ -n-1 HEX(31) = 0001 1111 --------------------------- - 结果 = 0000 0001 -# 计算得到位置是 1 -``` - -### 9. 快速失败(fail—fast) - -HashMap 遍历使用的是一种快速失败机制,它是 Java 非安全集合中的一种普遍机制,这种机制可以让集合在遍历时,如果有线程对集合进行了修改、删除、增加操作,会触发并发修改异常。 - -它的实现机制是在遍历前保存一份 modCount ,在每次获取下一个要遍历的元素时会对比当前的 modCount 和保存的 modCount 是否相等。 - -快速失败也可以看作是一种**安全机制**,这样在多线程操作不安全的集合时,由于快速失败的机制,会抛出异常。 - -### 10. 线程安全的 Map - -1. 使用 Collections.synchronizedMap(Map) 创建线程安全的 Map. - - 实现原理:有一个变量 `final Object mutex; ` ,操作方法都加了这个 `synchronized (mutex) ` 排它锁。 - -2. 使用 Hashtable - -3. 使用 ConcurrentHashMap - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/jvm/jvm-hotput.md b/docs/jvm/jvm-hotput.md deleted file mode 100644 index 3575ee4..0000000 --- a/docs/jvm/jvm-hotput.md +++ /dev/null @@ -1,416 +0,0 @@ ---- -title: 原来热加载如此简单,手动写一个 Java 热加载吧 -date: 2019-10-28 08:35:00 -url: jvm/java-hotput -tags: - - JVM - - 热加载 - - 热部署 -categories: - - Java 虚拟机 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - ![热加载](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/640.webp) - -# 1. 什么是热加载 - -**热加载**是指可以在不重启服务的情况下让更改的代码生效,**热加载**可以显著的提升开发以及调试的效率,它是基于 Java 的类加载器实现的,但是由于热加载的不安全性,一般不会用于正式的生产环境。 - -# 2. 热加载与热部署的区别 - -首先,不管是**热加载**还是热部署,都可以在不重启服务的情况下编译/部署项目,都是基于 Java 的类加载器实现的。 - -那么两者到底有什么区别呢? - -在部署方式上: - -- 热部署是在服务器运行时**重新部署**项目。 -- 热加载是在运行时**重新加载 class**。 - -在实现原理上: - -- 热部署是直接重新**加载整个应用**,耗时相对较高。 -- 热加载是在运行时**重新加载 class**,后台会启动一个线程不断检测你的类是否改变。 - -在使用场景上: - -- 热部署更多的是在**生产环境**使用。 -- 热加载则更多的是在**开发环境**上使用。线上由于安全性问题不会使用,难以监控。 - -# 3. 类加载五个阶段 - -![类的生命周期](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572188635986.png) - -可能你已经发现了,图中一共是7个阶段,而不是5个。是因为图是类的完整生命周期,如果要说只是类加载阶段的话,图里最后的使用(Using)和卸载(Unloading)并不算在内。 - -简单描述一下类加载的五个阶段: - -1. 加载阶段:找到类的静态存储结构,加载到虚拟机,定义数据结构。用户可以自定义类加载器。 - -2. 验证阶段:确保字节码是安全的,确保不会对虚拟机的安全造成危害。 - -3. 准备阶段:确定内存布局,确定内存遍历,赋**初始值**(注意:是初始值,也有特殊情况)。 - -4. 解析阶段: 将符号变成直接引用。 - -5. 初始化阶段:调用程序自定义的代码。规定有且仅有5种情况必须进行初始化。 - 1. new(实例化对象)、getstatic(获取类变量的值,被final修饰的除外,他的值在编译器时放到了常量池)、putstatic(给类变量赋值)、invokestatic(调用静态方法) 时会初始化 - 2. 调用子类的时候,发现父类还没有初始化,则父类需要立即初始化。 - 3. 虚拟机启动,用户要执行的主类,主类需要立即初始化,如 main 方法。 - 4. 使用 java.lang.reflect包的方法对类进行反射调用方法 是会初始化。 - 5. 当使用JDK 1.7的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后 - 的解析结果REF_getStatic、 REF_putStatic、 REF_invokeStatic的方法句柄, 并且这个方法句柄 - 所对应的类没有进行过初始化, 则需要先触发其初始化。 - -要说明的是,类加载的 5 个阶段中,只有加载阶段是用户可以自定义处理的,而验证阶段、准备阶段、解析阶段、初始化阶段都是用 JVM 来处理的。 - -# 4. 实现类的热加载 - -## 4.1 实现思路 - -我们怎么才能手动写一个类的热加载呢?根据上面的分析,Java 程序在运行的时候,首先会把 class 类文件加载到 JVM 中,而类的加载过程又有五个阶段,五个阶段中只有**加载阶段**用户可以进行自定义处理,所以我们如果能在程序代码更改且重新编译后,让运行的进程可以实时获取到新编译后的 class 文件,然后重新进行加载的话,那么理论上就可以实现一个简单的 **Java 热加载**。 - -所以我们可以得出实现思路: - -1. 实现自己的类加载器。 -2. 从自己的类加载器中加载要热加载的类。 -3. 不断轮询要热加载的类 class 文件是否有更新。 -4. 如果有更新,重新加载。 - -## 4.2 自定义类加载器 - -设计 Java 虚拟机的团队把类的加载阶段放到的 JVM 的外部实现( 通过一个类的全限定名来获取描述此类的二进制字节流 )。这样就可以让程序自己决定如果获取到类信息。而实现这个加载动作的代码模块,我们就称之为 “类加载器”。 - -在 Java 中,类加载器也就是 `java.lang.ClassLoader`. 所以如果我们想要自己实现一个类加载器,就需要继承 `ClassLoader` 然后重写里面 `findClass`的方法,同时因为类加载器是 `双亲委派模型`实现(也就说。除了一个最顶层的类加载器之外,每个类加载器都要有父加载器,而加载时,会先询问父加载器能否加载,如果父加载器不能加载,则会自己尝试加载)所以我们还需要指定父加载器。 - -最后根据传入的类路径,加载类的代码看下面。 - -```java -package net.codingme.box.classloader; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; - -/** - *

- * 自定义 Java类加载器来实现Java 类的热加载 - * - * @Author niujinpeng - * @Date 2019/10/24 23:22 - */ -public class MyClasslLoader extends ClassLoader { - - /** 要加载的 Java 类的 classpath 路径 */ - private String classpath; - - public MyClasslLoader(String classpath) { - // 指定父加载器 - super(ClassLoader.getSystemClassLoader()); - this.classpath = classpath; - } - - @Override - protected Class findClass(String name) throws ClassNotFoundException { - byte[] data = this.loadClassData(name); - return this.defineClass(name, data, 0, data.length); - } - - /** - * 加载 class 文件中的内容 - * - * @param name - * @return - */ - private byte[] loadClassData(String name) { - try { - // 传进来是带包名的 - name = name.replace(".", "//"); - FileInputStream inputStream = new FileInputStream(new File(classpath + name + ".class")); - // 定义字节数组输出流 - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - int b = 0; - while ((b = inputStream.read()) != -1) { - baos.write(b); - } - inputStream.close(); - return baos.toByteArray(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } -} -``` - -## 4.3 定义要热加载的类 - -我们假设某个接口(BaseManager.java)下的某个方法(logic)要进行热加载处理。 - -首先定义接口信息。 - -```java -package net.codingme.box.classloader; - -/** - *

- * 实现这个接口的子类,需要动态更新。也就是热加载 - * - * @Author niujinpeng - * @Date 2019/10/24 23:29 - */ -public interface BaseManager { - - public void logic(); -} -``` - -写一个这个接口的实现类。 - -```java -package net.codingme.box.classloader; - -import java.time.LocalTime; - -/** - *

- * BaseManager 这个接口的子类要实现类的热加载功能。 - * - * @Author niujinpeng - * @Date 2019/10/24 23:30 - */ -public class MyManager implements BaseManager { - - @Override - public void logic() { - System.out.println(LocalTime.now() + ": Java类的热加载"); - } -} -``` - -后面我们要做的就是让这个类可以通过我们的 MyClassLoader 进行自定义加载。类的**热加载**应当只有在类的信息被更改然后重新编译之后进行重新加载。所以为了不意义的重复加载,我们需要判断 class 是否进行了更新,所以我们需要记录 class 类的修改时间,以及对应的类信息。 - -所以编译一个类用来记录某个类对应的某个类加载器以及上次加载的 class 的修改时间。 - -```java -package net.codingme.box.classloader; - -/** - *

- * 封装加载类的信息 - * - * @Author niujinpeng - * @Date 2019/10/24 23:32 - */ -public class LoadInfo { - - /** 自定义的类加载器 */ - private MyClasslLoader myClasslLoader; - - /** 记录要加载的类的时间戳-->加载的时间 */ - private long loadTime; - - /** 需要被热加载的类 */ - private BaseManager manager; - - public LoadInfo(MyClasslLoader myClasslLoader, long loadTime) { - this.myClasslLoader = myClasslLoader; - this.loadTime = loadTime; - } - - public MyClasslLoader getMyClasslLoader() { - return myClasslLoader; - } - - public void setMyClasslLoader(MyClasslLoader myClasslLoader) { - this.myClasslLoader = myClasslLoader; - } - - public long getLoadTime() { - return loadTime; - } - - public void setLoadTime(long loadTime) { - this.loadTime = loadTime; - } - - public BaseManager getManager() { - return manager; - } - - public void setManager(BaseManager manager) { - this.manager = manager; - } -} -``` - -## 4.4 热加载获取类信息 - -在实现思路里,我们知道轮询检查 class 文件是不是被更新过,所以每次调用要热加载的类时,我们都要进行检查类是否被更新然后决定要不要重新加载。为了方便这步的获取操作,可以使用一个简单的工厂模式进行封装。 - -要注意是加载 class 文件需要指定完整的路径,所以类中定义了 CLASS_PATH 常量。 - -```java -package net.codingme.box.classloader; - -import java.io.File; -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; - -/** - *

- * 加载 manager 的工厂 - * - * @Author niujinpeng - * @Date 2019/10/24 23:38 - */ -public class ManagerFactory { - - /** 记录热加载类的加载信息 */ - private static final Map loadTimeMap = new HashMap<>(); - - /** 要加载的类的 classpath */ - public static final String CLASS_PATH = "D:\\IdeaProjectMy\\lab-notes\\target\\classes\\"; - - /** 实现热加载的类的全名称(包名+类名 ) */ - public static final String MY_MANAGER = "net.codingme.box.classloader.MyManager"; - - public static BaseManager getManager(String className) { - File loadFile = new File(CLASS_PATH + className.replaceAll("\\.", "/") + ".class"); - // 获取最后一次修改时间 - long lastModified = loadFile.lastModified(); - System.out.println("当前的类时间:" + lastModified); - // loadTimeMap 不包含 ClassName 为 key 的信息,证明这个类没有被加载,要加载到 JVM - if (loadTimeMap.get(className) == null) { - load(className, lastModified); - } // 加载类的时间戳变化了,我们同样要重新加载这个类到 JVM。 - else if (loadTimeMap.get(className).getLoadTime() != lastModified) { - load(className, lastModified); - } - return loadTimeMap.get(className).getManager(); - } - - /** - * 加载 class ,缓存到 loadTimeMap - * - * @param className - * @param lastModified - */ - private static void load(String className, long lastModified) { - MyClasslLoader myClasslLoader = new MyClasslLoader(className); - Class loadClass = null; - // 加载 - try { - loadClass = myClasslLoader.loadClass(className); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - - BaseManager manager = newInstance(loadClass); - LoadInfo loadInfo = new LoadInfo(myClasslLoader, lastModified); - loadInfo.setManager(manager); - loadTimeMap.put(className, loadInfo); - } - - /** - * 以反射的方式创建 BaseManager 的子类对象 - * - * @param loadClass - * @return - */ - private static BaseManager newInstance(Class loadClass) { - try { - return (BaseManager)loadClass.getConstructor(new Class[] {}).newInstance(new Object[] {}); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } - return null; - } -} -``` - -## 4.5 热加载测试 - -直接写一个线程不断的检测要热加载的类是不是已经更改需要重新加载,然后运行测试即可。 - -```java -package net.codingme.box.classloader; - -/** - *

- * - * 后台启动一条线程,不断检测是否要刷新重新加载,实现了热加载的类 - * - * @Author niujinpeng - * @Date 2019/10/24 23:53 - */ -public class MsgHandle implements Runnable { - @Override - public void run() { - while (true) { - BaseManager manager = ManagerFactory.getManager(ManagerFactory.MY_MANAGER); - manager.logic(); - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } -} -``` - -主线程: - -```java -package net.codingme.box.classloader; - -public class ClassLoadTest { - public static void main(String[] args) { - new Thread(new MsgHandle()).start(); - } -} -``` - -代码已经全部准备好了,最后一步,可以启动测试了。如果你是用的是 Eclipse ,直接启动就行了;如果是 IDEA ,那么你需要 DEBUG 模式启动(IDEA 对热加载有一定的限制)。 - -启动后看到控制台不断的输出: - -```shell -00:08:13.018: Java类的热加载 -00:08:15.018: Java类的热加载 -``` - -这时候我们随便更改下 MyManager 类的 logic 方法的输出内容然后保存。 - -```java -@Override -public void logic() { - System.out.println(LocalTime.now() + ": Java类的热加载 Oh~~~~"); -} -``` - -可以看到控制台的输出已经自动更改了(IDEA 在更改后需要按 CTRL + F9)。 - -![类的热加载](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572192565262.png) - -代码已经放到Github: https://github.com/niumoo/lab-notes/ - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/linux/linux-crontab.md b/docs/linux/linux-crontab.md deleted file mode 100644 index c584328..0000000 --- a/docs/linux/linux-crontab.md +++ /dev/null @@ -1,140 +0,0 @@ ---- -title: Linux定时任务crontab的使用 -date: 2018-05-23 22:14:25 -url: linux/linux-crontab -tags: - - Crontab - - 定时任务 -categories: - - Linux -thumbnail: /static/blog/images/logo/linux-crontab.png ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![crontab logo](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/d82883e1a2b8c5b9e949e80047091e74.jpg) - - - -### crontab介绍 - -crontab经常被用于设置周期性任务,Linux系统本身就存在的许多计划性任务是由cron服务来控制,因此默认情况下,这个cron服务也是默认启动的。crond常常在后台运行,每一分钟检查是否有预定的作业需要执行。Linux 系统为使用者控制计划任务提供的命令:`crontab 命令`。 - - -### crontab文件 -`Linux的系统任务调度文件`,可以在/etc/crontab文件中查看系统计划任务信息。在CentOS7中文件内容如下: -```shell -[root@VM_105_191_centos ~]# cat /etc/crontab -SHELL=/bin/bash #使用哪个shell -PATH=/sbin:/bin:/usr/sbin:/usr/bin #系统命令的路径 -MAILTO=root #任务执行结果通知给root用户,不写则不通知 - -# For details see man 4 crontabs -# 任务命令格式说明 -# Example of job definition: -# .---------------- minute (0 - 59) -# | .------------- hour (0 - 23) -# | | .---------- day of month (1 - 31) -# | | | .------- month (1 - 12) OR jan,feb,mar,apr ... -# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat -# | | | | | -# * * * * * user-name command to be executed -``` -`Linux的用户任务调度文件`,系统会把用户使用crontab工具定制的周期性计划任务保存在:`/var/spool/cron/`目录中,里面的文件名和用户名相同,对应每个用户,可以直接查看。 - -### 计划任务时间设置 -上面的Crontab文件没有任何计划任务,但是有一份详细的crontab文件格式说明,我们按照文件中的内容可以很好的理解五个*号区域所代表的意思。 -```shell -# 指令格式说明: -# .---------------- 分 (0 - 59) -# | .------------- 时 (0 - 23) -# | | .---------- 日 (1 - 31) -# | | | .------- 月 (1 - 12) -# | | | | .---- 星期 (0 - 6) (星期日=0 或 7) -# | | | | | -# * * * * * 操作指令 -``` -这里需要注意的是,如果日期和星期同时被设定,那么其中的一个条件被满足时,指令便会被执行。 -在上面的每个区域中, - -| 说明 | -| :-------- : | -|星号('*')代表任何可能的值。例如,在“小时域”里的星号等于是“每一个小时”,等等 | -|逗号(',')分开的值,例如:“1,3,4,7,8” | -|连词符('-')指定值的范围,例如:“1-6”,意思等同于“1,2,3,4,5,6” | -|某些cron程序的扩展版本也支持斜线('/')操作符,例如,“*/3”在小时域中等于“0,3,6,9,12,15,18,21”等被3整除的数; | - -实例:每1分钟输出一次hello -命令:\* \* \* \* \* command - -实例:每小时的第3和第15分钟执行 -命令:3,15 \* \* \* \* command - -实例:在上午8点到11点的第3和第15分钟执行 -命令:3,15 8-11 \* \* \* command - -### crontab命令 -命令中的`File`是命令文件的名字,在这个文件中编写了符合上述规则的计划任务,命令则表示将这个File做为crontab的任务载入到crontab。如果在命令行中没有指定这个文件,crontab命令将接受标准输入(键盘)上键入的命令,并将它们载入crontab。载入crontab之后可以在`/var/spool/cron/[user]`文件查看计划任务信息。 -```shell -用法: - crontab [参数] 文件 - crontab [参数] - -常用参数: - -u 配置用户计划任务,默认当前用户 - -e 编辑用户计划任务,默认当前用户 - -l 列出用户计划任务,默认当前用户更 - -r 清空用户计划任务,默认当前用户 - -i 删除用户计划任务,有确认提示 -``` - -所以,如果要把一个编写好的crontab文件添加到当前用户的计划任务,可以使用 -$ crontab crontab_file - -### 一些示例 -实例1:每1分钟执行一次myCommand -\* \* \* \* \* myCommand - -实例2:每小时的第3和第15分钟执行 -3,15 \* \* \* \* myCommand - -实例3:在上午8点到11点的第3和第15分钟执行 -3,15 8-11 \* \* \* myCommand - -实例4:每隔两天的上午8点到11点的第3和第15分钟执行 -3,15 8-11 \*/2 \* \* myCommand - -实例5:每周一上午8点到11点的第3和第15分钟执行 -3,15 8-11 \* \* 1 myCommand - -实例6:每晚的21:30重启smb -30 21 \* \* \* /etc/init.d/smb restart - -实例7:每月1、10、22日的4 : 45重启smb -45 4 1,10,22 \* \* /etc/init.d/smb restart - -实例8:每周六、周日的1 : 10重启smb -10 1 \* \* 6,0 /etc/init.d/smb restart - -实例9:每天18 : 00至23 : 00之间每隔30分钟重启smb -0,30 18-23 \* \* \* /etc/init.d/smb restart - -实例10:每星期六的晚上11 : 00 pm重启smb -0 23 \* \* 6 /etc/init.d/smb restart - -实例11:每一小时重启smb -\* \*/1 \* \* \* /etc/init.d/smb restart - -实例12:晚上11点到早上7点之间,每隔一小时重启smb -0 23-7 \* \* \* /etc/init.d/smb restart - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/linux/linux-install-nginx.md b/docs/linux/linux-install-nginx.md deleted file mode 100644 index bbc5755..0000000 --- a/docs/linux/linux-install-nginx.md +++ /dev/null @@ -1,133 +0,0 @@ ---- -title: 在CentOS7下Nginx的简单使用 -date: 2018-07-10 23:05:10 -url: linux/install-nginx -tags: - - Nginx -categories: - - Linux ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - - -![Nginx Logo](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/060ce880b9ad8d2688139d38ce01b355.jpg) -### Nginx -Nginx是一个异步框架的 Web服务器,也可以用作反向代理,负载平衡器 和 HTTP缓存。该软件由 Igor Sysoev 创建,并于2004年首次公开发布。 同名公司成立于2011年,以提供支持。 - -安装 -```shell -yum -y install nginx -``` -启动 -```shell -// 启动使用start 检查是否启动使用status 停止stop -service nginx start -``` - -测试 -输入IP地址进行访问,出现下图说明已经成功 -![nginx访问](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/cd56f7d9e0b1df3d6bf1e9e668d09cf9.jpg) - -### Nginx简单配置 - 发布web目录 -nginx 的配置文件位置在/etc/nginx/nginx.conf ,在修改之前建议进行备份! -```shell -// 备份配置文件 -cp nginx.conf nginx.conf.bak -// 编辑Nginx配置文件 -vim /etc/nginx/nginx.conf -// 在配置文件的http{}中添加如下配置 -server { - # 端口号 - listen 80; - # 域名 - server_name codingme.net; - # web目录 - root /webroot; - # 默认首页 - index index.html - error_page 500 502 503 504 /50x.html; - location / { - } - } -``` -### Nginx简单配置 - 反向代理 - -什么是反向代理呢?服务器根据客户端的请求,从一组或多组后端服务器(如Web服务器)上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器簇的存在。 -一图胜千言: -![反向代理](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ed853d7219f3414a1676b0e411e40018.png) -使用反向代理有如下功能,引用维基百科: - -> 对客户端隐藏服务器(簇)的IP地址 -> 安全:作为应用层防火墙,为网站提供对基于Web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等 -> 为后端服务器(簇)统一提供加密和SSL加速(如SSL终端代理) -> 负载均衡,若服务器簇中有负荷较高者,反向代理通过URL重写,根据连线请求从负荷较低者获取与所需相同的资源或备援 -> 对于静态内容及短时间内有大量访问请求的动态内容提供缓存服务 -> 对一些内容进行压缩,以节约带宽或为网络带宽不佳的网络提供服务 -> 减速上传 -> 为在私有网络下(如局域网)的服务器簇提供NAT穿透及外网发布服务 -> 提供HTTP访问认证[2] -> 突破互联网封锁(不常用,因为反向代理与客户端之间的连线不一定是加密连线,非加密连线仍有遭内容审查进而遭封禁的风险;此外面对针对域名的关键字过滤、DNS缓存污染/投毒攻击乃至深度数据包检测也无能为力) - -```shell -// 编辑Nginx配置文件 -vim /etc/nginx/nginx.conf -// 在配置文件的http{}中添加如下配置 - server { - listen 80; - server_name codingme.net; - location / { - # Tomcat访问路径 - proxy_pass http://127.0.0.1:8080/; - } - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } - } -``` -### Nginx简单配置 - HTTPS -在Google对HTPPS的推动下,越来越多的网站开始启用HTTPS协议,以应对网络安全问题。 - -关于HTTPS的概念,推荐一篇文章:[HTTPS 的故事](https://qianduan.group/posts/5a6560b00cf6b624d2239c6f) - -```shell -// 编辑Nginx配置文件 -vim /etc/nginx/nginx.conf -// 在配置文件的http{}中添加如下配置 -server { - listen 80; - server_name codingme.net; - # HTTP请求跳转至HTTPS - rewrite ^(.*) https://$host$1 permanent; - } -server { - listen 443; - server_name codingme.net; - ssl on; - # HTTPS证书位置 - ssl_certificate /webroot/xxx.crt; - # 密钥位置 - ssl_certificate_key /webroot/xxx.key; - ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; - ssl_prefer_server_ciphers on; - # 网站目录 - root /webroot; - error_page 500 502 503 504 /50x.html; - location / { - - } - } -``` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/linux/linux-manjaro.md b/docs/linux/linux-manjaro.md deleted file mode 100644 index 9acc7fe..0000000 --- a/docs/linux/linux-manjaro.md +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Manjaro Linux 入门使用教程 -date: 2020-04-24 08:08:01 -url: linux/linux-manjaro -tags: - - Manjaro - - Manjaro 软件安装 -categories: - - Linux ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![screenfetch](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200419220905196.png) - -## Manjaro 初体验 - -Manjaro 是一款基于 Arch LInux 的自由开源发行版,它吸收了 Arch Linux 优秀丰富的软件管理,同时提供了稳定流畅的操作体验。优雅简单是它的追求,稳定实用是它的优势。 - -Manjaro 和 Arch Linux 一样采用滚动发行模式,但是它的滚动更新是在 Arch Linux 更新测试一段时间之后,这也保证了系统的稳定性。话虽如此,使用中你依旧可能面临大量的更新而不知如何选择,所以,**如果使用已经满足需求,有升级的必要吗?** - -### 下载镜像 - -镜像下载可以去 [Manjaro](https://manjaro.org/get-manjaro/) 官方网站下载,国内速度太慢也可以到[ 清华大学开源软件镜像站](https://mirrors.tuna.tsinghua.edu.cn/osdn/storage/g/m/ma/manjaro/) 进行下载。Manjaro 提供了多种桌面环境,可以根据喜好自行下载,我一般偏向于 kde 或者 gnome 桌面。 - -### 制作启动盘 - -使用 [Rufus](http://rufus.ie/) 工具以 **DD 模式**写入镜像到 U 盘,制作 U 盘启动成功之后,开机选择 U 盘进行启动即可。Rufus 工具这里选择的是 3.4 版本,经过测试,高版本的 Rufus 可能会存在分区类型等选项不能修改的 :bug: Bug。 - -![Rufus 3.4](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200421213247256.png) - -### 安装 Manjaro - -安装 Manjaro 这里不做描述,网上有很多优秀的教程可以参考。总体来说 Manjaro 安装还是比较轻松的,相比其他的 Linux 发行版,安装体验更好。特别是对显卡驱动方面的支持,一键安装,特别省心。 - -## 更换软件源 - -更换软件源为国内清华大学源,安装软件更迅速。 - -```shell -sudo pacman -Syy -sudo pacman-mirrors -i -c China -m rank #选一个清华源就行 -#sudo pacman -Syyu -``` - -在弹出的窗口里选择一个镜像源即可,我这里选择的是清华大学镜像源。 - -![选择清华镜像源](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200419181748778.png) - -```shell -sudo vim /etc/pacman.conf -# 下面的内容添加到文件 -[archlinuxcn] -SigLevel = Optional TrustedOnly -Server = https://mirrors.ustc.edu.cn/archlinuxcn/$arch -# 执行更新,导入GPG key -sudo pacman -Syy && sudo pacman -S archlinuxcn-keyring -``` - -## 安装输入法 - -```shell -sudo pacman -S fcitx-sogoupinyin -sudo pacman -S fcitx-im # 全部安装 -sudo pacman -S fcitx-configtool # 图形化配置工具 -``` - -设置中文输入法环境变量,编辑~/.xprofile文件,增加下面几行(如果文件不存在,则新建) - -```shell -export GTK_IM_MODULE=fcitx -export QT_IM_MODULE=fcitx -export XMODIFIERS="@im=fcitx" -``` - -## 使用 zsh - -没体验过 zsh 的建议试试,命令敲起来十分顺畅。 - -```shell -sudo pacman -S zsh -# 下载这个 install.sh 自行运行 -# https://github.com/ohmyzsh/ohmyzsh/blob/master/tools/install.sh -# 下面这种方式已经失效 -#sh -c "$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" -chsh -s /bin/zsha -``` - -## 安装常用软件 - -99% 的软件只需要几条命令就可以安装,像下面这样。 - -```shell -# 生成 ssh 密钥 ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -sudo pacman -S git -sudo pacman -S vim -sudo pacman -S visual-studio-code-bin # vscode -sudo pacman -S shadowsocks-qt5 -sudo pacman -S google-chrome # 谷歌浏览器 -sudo pacman -S netease-cloud-music # 网易云音乐 -sudo pacman -S wps-office -``` - -网易云音乐还有一个基于 Python 编写的开源的命令行版本,使用命令行播放操控歌曲,十分极客炫酷,有兴趣的朋友可以尝试。 - -NetEase-MusicBox 开源地址:[https://github.com/darknessomi/musicbox](https://github.com/darknessomi/musicbox) - -安装坚果云。 - -```shell -# 下载坚果云安装包 -wget https://www.jianguoyun.com/static/exe/installer/nutstore_linux_dist_x64.tar.gz -# 安装坚果云,解压后运行 -./bin/install_core.sh -# 安装所需依赖 -sudo pacman -S gvfs libappindicator-gtk3 python2-gobject -``` - -安装 TIM / QQ. - -```shell -sudo pacman -S deepin.com.qq.office -# 由于 qq 依赖了 cinnamon-settings-daemon -sudo pacman -S cinnamon-settings-daemon -/usr/lib/cinnamon-settings-daemon/csd-xsettings -# 可以尝试将上方的 csd-xsettings 加入到开自启 -# 修改 TIM 字体大小,下面命令之后-》显示 DPI 120 -env WINEPREFIX="$HOME/.deepinwine/Deepin-TIM" /usr/bin/deepin-wine winecfg -``` - -## 开发环境配置 - -安装 JDK,配置环境变量。 - -```shell -export JAVA_HOME=/home/niu/develop/program/jdk1.8.0_191 -export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -export PATH=$JAVA_HOME/bin:$PATH -``` - -安装 maven,配置环境变量。 - -```shell -export M2_HOME=/home/niu/program/apache-maven-3.6.3 -export PATH=$PATH:$M2_HOME/bin -``` - -## 字体渲染 - -字体渲染的好坏直接影响到使用体验,这里推荐下面几款字体。 - -```shell -sudo pacman -S ttf-roboto noto-fonts ttf-dejavu -# 文泉驿 -sudo pacman -S wqy-bitmapfont wqy-microhei wqy-microhei-lite wqy-zenhei -# 思源字体 -sudo pacman -S noto-fonts-cjk adobe-source-han-sans-cn-fonts adobe-source-han-serif-cn-fonts -``` - -你也可以从下面的 Git 仓库中下载微软 windows 10 字体,获得和 windows 相似的字体体验。Github 下载速度较慢,我已经克隆了一份到 Gitee 码云。 - -GIthub:[https://github.com/fphoenix88888/ttf-mswin10-arch](https://github.com/fphoenix88888/ttf-mswin10-arch) - -Gitee:[https://gitee.com/niumoo/ttf-mswin10-arch](https://gitee.com/niumoo/ttf-mswin10-arch) - -你也可以自己下载喜欢的字体复制到 `/usr/share/fonts/TTF` 文件夹下。然后使用命令 `fc-cache -fv` 刷新字体。 - -有时候你已经安装了不错的字体,显示效果还是不好,可以尝试调整设置里的屏幕缩放和强制字体 DPI 参数,缩放我一般不建议调整,可以调整字体 DPI 为 120 或者 144。 - -## 可选操作 - -1. 系统更新 - -```shell -# 更新所有软件系统 -sudo pacman -Syyu -``` - -如果你对更新内容不是很了解,对 Linux 操作还不熟练,那么我给你的建议是没问题不要更新,不然更新之后遇到一些问题之后你可能无法搞定。当然这个概率很小。 - -![sudo pacman -Syyu 更新系统](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200420080215272.png) - -2. 显卡驱动 - -安装显卡驱动,如果你开机关机没有任何问题,就不要折腾了。如果你不幸开机或者关机卡死,可以尝试安装一下驱动,在硬件设定里点击 **Auto Install Proprietary Driver** 自动检测安装,这个显卡驱动自动检测安装是我喜欢 Manjaro 的原因之一。。 - -![安装显卡驱动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200420215953766.png) - -3. 垃圾清理 - -清除系统中无用的包。 - -```shell -sudo pacman -R $(pacman -Qdtq) -``` - -清除已下载的安装包。 - -```shell -sudo pacman -Scc -``` - -## 总结 - -几天使用下来,Manjaro 的体验比想象中的要好,在这之前我也体验过把 Deepin 和 Ubunut 作为主力系统,Deepin 对于某些机器显卡驱动不是特别友好,经常会开机或者关机卡死,但是桌面环境相对优秀。而 Ubuntu 在作为桌面环境使用时,经常会出现莫名的内部错误,安装软件有时候比较繁琐,当然 Ubuntu 的优点也很多,不错的界面,活跃的社区等。在Manjaro 的体验中我发现困扰很久的显卡驱动问题竟然可以如此轻松的解决。KDE 桌面环境也很舒服,目前不尽人意的地方在于字体渲染,不管我是调整缩放还是调整字体 DPI 效果都不明显。可能是我没有找到正确的方法吧,毕竟有的朋友可以开箱即用。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/linux/linux-ubuntu-start.md b/docs/linux/linux-ubuntu-start.md deleted file mode 100644 index 19d1041..0000000 --- a/docs/linux/linux-ubuntu-start.md +++ /dev/null @@ -1,352 +0,0 @@ ---- -title: Ubuntu18 的超详细常用软件安装 -date: 2018-11-16 09:51:11 -url: linux/start-ubuntu -tags: - - Ubuntu - - Ubuntu软件安装 -categories: - - Linux ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Ubuntu-desktop](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/877fecc7f272598c29084c2a621df2c1.png) - -心血来潮,在笔记本安装了Ubuntu 18 用于日常学习,于是有了下面的安装记录。 -## Gnome-Tweak-Tool - -gnome-tweak-tool可以打开隐藏的设置,可以详细的对系统进行配置,以及安装主题和扩展等功能。 - -```shell -// 安装 -sudo apt install gnome-tweak-tool -// 安装扩展 -sudo apt install gnome-shell-extensions -alt+f2 r 回车 -// 安装浏览器扩展工具 -sudo apt install chrome-gnome-shell -``` - - - -## N卡驱动异常 -### 开机关机异常 -因为使用了N卡`开源`驱动,`N卡驱动`和ubuntu系统的兼容性存在问题,有时会导致无法开机,开机循环登录,关机注销卡死等一系列问题,如果你也碰到这样的问题,可以继续向下看。笔者在使用过程中需要同样问题,升级驱动无果之后决定直接禁用掉N卡驱动。 - -开机先进入登录页面,`CTRL+ALT+F2`进入命令行模式。 - -```shell -// purge(彻底删除软件和配置) -sudo apt-get purge nvidia-* -// 进入 /配置文件/自动载入模块(类似windows系统下的服务)配置文件 -cd /etc/modprobe.d/ -// 使用vim 编辑(不存在会新建)一个叫blacklist-nouveau.conf的文件 -sudo vim blacklist-nouveau.conf -// 在编辑模式下,按i(insert)进入编辑模式,输入 -blacklist nouveau -options nouveau modeset=0 -// 按一次esc退出编辑模式,再按一次“冒号”,输入wq(保存并退出) -// 重置内核引导 -sudo update-initramfs -u -// 重启ubuntu -sudo reboot -``` -重启之后就可以正常登录进桌面了,但是笔者发现当连接多个显示器的时候,不能进行扩展显示,应该是没有N卡驱动影响到的,如果没有多个显示,那么可以就此停止折腾了。 - -### 安装N卡驱动 - -无奈有两个显示器,不用起来还是有点不舒服的,因此有了下面的操作,安装nvidia官方驱动。 - -把 nouveau 驱动加入黑名单 - -```shell -$sudo nano /etc/modprobe.d/blacklist-nouveau.conf -``` -在文件 blacklist-nouveau.conf 中加入如下内容: -```shell -blacklist nouveau -blacklist lbm-nouveau -options nouveau modeset=0 -alias nouveau off -alias lbm-nouveau off -``` -禁用 nouveau 内核模块 -```shell -$echo options nouveau modeset=0 | sudo tee -a /etc/modprobe.d/nouveau-kms.conf -$sudo update-initramfs -u -``` -可以用lsmod看看禁止成功没有 -```shell -lsmod | grep nouveau -``` -然后开始安装Nvidia驱动 -```shell -sudo add-apt-repository ppa:graphics-drivers/ppa -sudo apt update -sudo ubuntu-drivers autoinstall -``` -重启 -```shell -sudo apt install nvidia-cuda-toolkit gcc-6 -nvcc --version -``` -用lsmod看看驱动安装成功没有 -```shell -lsmod | grep nvidia -``` -安装cuda-toolkit,介绍可以参考 [https://developer.nvidia.com/cuda-toolkit](https://developer.nvidia.com/cuda-toolkit) -```shell -sudo apt install nvidia-cuda-toolkit gcc-6 -nvcc --version -``` - -## 纸飞机Shadowsocks -```shell -sudo apt-get update -sudo apt install shadowsocks -// 自行编写配置文件 /etc/shadowsocks.json -// 启动 -sslocal -c /etc/shadowsocks.json -``` - -## JDK环境变量 - -JDK下载解压此处不说。环境变量配置如下。 - -```shell -export JAVA_HOME=/home/niu/develop/program/jdk1.8.0_191 -export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar -export PATH=$JAVA_HOME/bin:$PATH - -export JAVA_HOME=/home/niu/develop/program/jdk1.8.0_191 -export JRE_HOME=/home/niu/develop/program/jdk1.8.0_191/jre -export CLASSPATH=.:$JAVA_HOME/lib:$JRE_HOME/lib:$CLASSPATH -export PATH=$JAVA_HOME/bin:$JRE_HOME/bin:$PATH -``` - -## IDEA图标 -IDEA下载解压此处不说。 - -/usr/share/applications目录下,如果我们要创建桌面快捷方式,需要在该目录下创建一个名为“idea.desktop”的文件。 - -```shell -[Desktop Entry] -Name=IdeaIU -Comment=IdeaIU -Exec=env JAVA_HOME=/home/niu/develop/program/jdk1.8.0_191 /home/niu/develop/program/idea-IU-182.4892.20/bin/idea.sh -Icon=/home/niu/develop/program/idea-IU-182.4892.20/bin/idea.png -Terminal=false -Type=Application -Categories=Application;Development; -``` - -## Sublime Text 3 -```shell -wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | sudo apt-key add - -echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list - -sudo apt-get update -sudo apt-get install sublime-text -``` -## 字体YaHeiConsolas -```shell -wget http://www.mycode.net.cn/wp-content/uploads/2015/07/YaHeiConsolas.tar.gz -tar -zxvf YaHeiConsolas.tar.gz -sudo mkdir -p /usr/share/fonts/YaHeiConsolas -sudo cp YaHeiConsolas.ttf /usr/share/fonts/YaHeiConsolas -cd /usr/share/fonts/YaHeiConsolas -sudo chmod 644 YaHeiConsolas.ttf -sudo mkfontscale -sudo mkfontdir -sudo fc-cache -fv -``` -## SecureCRT - -直接到官网注册下载。下载完毕之后可以试用30天。 - -下面是注册信息的生成,可能不适用于最新版本。 - -```shell -➜ software sudo perl securecrt_linux_crack.pl /usr/bin/SecureCRT -crack successful - -License: - - Name: xiaobo_l - Company: www.boll.me - Serial Number: 03-94-294583 - License Key: ABJ11G 85V1F9 NENFBK RBWB5W ABH23Q 8XBZAC 324TJJ KXRE5D - Issue Date: 04-20-2017 -``` - -## mysql5.7 -### 安装Mysql5.7 -```shell -# 安装mysql服务 -sudo apt-get install mysql-server -# 安装客户端 -sudo apt install mysql-client -# 安装依赖 -sudo apt install libmysqlclient-dev -# 检查状态 -sudo netstat -tap | grep mysql -``` -> mysql5.7安装完成后普通用户不能进mysql,原因:root的plugin被修改成了auth_socket,用密码登陆的plugin应该是mysql_native_password,直接用root权限登录就不用密码,修改root密码和登录验证方式。 - -```shell -# root权限进入mysql -sudo mysql -mysql> select user, plugin from mysql.user; -+------------------+-----------------------+ -| user | plugin | -+------------------+-----------------------+ -| root | auth_socket | -| mysql.session | mysql_native_password | -| mysql.sys | mysql_native_password | -| debian-sys-maint | mysql_native_password | -+------------------+-----------------------+ -4 rows in set (0.00 sec) - -mysql> update mysql.user set authentication_string=PASSWORD('123456'), plugin='mysql_native_password' where user='root'; -Query OK, 1 row affected, 1 warning (0.01 sec) -Rows matched: 1 Changed: 1 Warnings: 1 - -mysql> flush privileges; -Query OK, 0 rows affected (0.01 sec) - -mysql> exit -Bye -# 重启mysql -niu@ubuntu:~$ sudo /etc/init.d/mysql restart -[ ok ] Restarting mysql (via systemctl): mysql.service. -``` -### 远程登录mysql -```shell -# 修改配置文件,注释掉bind-address = 127.0.0.1 -niu@ubuntu:~$ sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf -niu@ubuntu:~$ mysql -uroot -p -Enter password: - -mysql> grant all on *.* to root@'%' identified by '123456' with grant option; -Query OK, 0 rows affected, 1 warning (0.00 sec) - -mysql> flush privileges; -Query OK, 0 rows affected (0.00 sec) - -mysql> exit -Bye -# 重启mysql -niu@ubuntu:~$ sudo /etc/init.d/mysql restart -``` - -## 安装typora -```shell -// or run: -// sudo apt-key adv --keyserver keyserver.ubuntu.com--recv-keys BA300B7755AFCFAE -wget -qO - https://typora.io/linux/public-key.asc | sudo apt-key add - -// add Typora's repository -sudo add-apt-repository 'deb https://typora.io/linux ./' -sudo apt-get update -// install typora -sudo apt-get install typora -``` -## 邮件客户端 -```shell -wget https://github.com/nylas/nylas-mail/releases/download/2.0.14/NylasMail-2.0.14.deb -sudo dpkg -i NylasMail-2.0.14.deb -sudo apt-get -f install -``` -安装之后发现要连接服务器,但是服务器报错,且了解到需要收费,因此放弃。 - -改用mailspring,界面好评,使用一天之后发现在邮件很多的时候会卡顿,还会出现服务器连接不上的情况,且没有设置pop3的地方,只有imap设置。因此放弃。 - -最后改用大名鼎鼎ThunderBird。 - -```shell -sudo apt-get install thunderbird-locale-uk thunderbird-locale-vi thunderbird-locale-zh-cn -``` - - - -## 安装搜狗拼音输入法 - -```shell -// 卸载自带的中文输入法 -sudo apt remove 'ibus*' -// 安装fcitx输入法配置框架 -sudo apt install fcitx-bin fcitx-table -// 在设置语言中,选择语言输入框架为fcitx,应用到整个系统。 -// 下载搜狗拼音linux版本 -https://pinyin.sogou.com/linux/ -// 搜狗拼音的官方安装教程,可以参考,也就是说先安装fcitx框架,然后安装输入法 -//https://pinyin.sogou.com/linux/help.php -// 双击安装 -``` -卸载搜狗拼音。 - -```shell - sudo apt-get remove sogoupinyin - sudo apt-get purge sogoupinyin - sudo apt-get autoremove -``` - -## VLC播放器 -```shell -安装解码器 -sudo apt-get install ubuntu-restricted-extras -安装VLC -sudo apt-get install vlc browser-plugin-vlc -``` - -## 点击任务栏图表最小化 -```shell -gsettings set org.gnome.shell.extensions.dash-to-dock click-action 'minimize' -``` - -## QQ TIM 迅雷 -Linux下QQ,TIM 一直体验不好,庆幸发现了目前体验最好的deepin 移植版。 -直接看[链接](https://github.com/wszqkzqk/deepin-wine-ubuntu) - -## wine程序图标放到顶部 - -使用这个功能需要先安装gnome-tweak-tool以及gnome-shell-extensions - -https://extensions.gnome.org/extension/1031/topicons/ - -```shell -TopIcons Plus -Applications Menu -``` - -## 登录页面背景 - -18.04登录背景相关的配置是用css的:/etc/alternatives/gdm3.css。如果你熟悉CSS规则, 可以很方便的编写出自己喜欢的登录页面样式。 - - -```css -//找到默认的这个部分 -lockDialogGroup { - background: #2c001e url(resource:///org/gnome/shell/theme/noise-texture.png); - background-repeat: repeat; -} -//改为 -lockDialogGroup { - background: #2c001e url(file:///usr/share/backgrounds/mypicture.jpg); - background-repeat: no-repeat; - background-size: cover; - background-position: center; -} -``` - -![Ubuntu-desktop](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7bcdeb5f8902fd0be6662bbb6397de32.png) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/io-blocking .md b/docs/mq/io-blocking .md deleted file mode 100644 index c0466df..0000000 --- a/docs/mq/io-blocking .md +++ /dev/null @@ -1,363 +0,0 @@ ---- -title: IO通信模型(一)同步阻塞模式BIO(Blocking IO) -date: 2018-10-23 17:44:42 -url: io/io1-bio -tags: - - BIO - - IO - - 通信模型 - - Blocking IO - - 同步阻塞IO -categories: - - 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/fe0a011dcd86762f98eecdedfad9eb6d.jpg) - -### 几个概念 - -**阻塞IO** 和**非阻塞IO** 这两个概念是程序级别的。主要描述的是程序请求操作系统IO操作后,如果IO资源没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行(但是使用线程一直轮询,直到有IO资源准备好了)。 - -**同步IO** 和 **异步IO**,这两个概念是操作系统级别的。主要描述的是操作系统在收到程序请求IO操作后,如果IO资源没有准备好,该如何响应程序的问题:前者不响应,直到IO资源准备好以后;后者返回一个标记(好让程序和自己知道以后的数据往哪里通知),当IO资源准备好以后,再用事件机制返回给程序。 - - -### 同步阻塞模式(Blocking IO) - -同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时如果数据没有准备号会被阻塞。 - -![BIO](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/63d5d1de854fe7ba06ab60f7a765ab01.png) -图片来源:www.masterraghu.com - -伪代码表示如下: - -```java -{ - // 阻塞,直到有数据 - read(socket, buffer); - process(buffer); -} -``` - -BIO通信方式的`特点`: - -1. 一个线程负责连接,多线程则为每一个接入开启一个线程。 -2. 一个请求一个应答。 -3. 请求之后应答之前客户端会一直等待(阻塞)。 - -BIO通信方式在单线程服务器下一次只能处理一个请求,在处理完毕之前一直阻塞。因此不适用于高并发的情况。不过可以使用多线程`稍微`改进。 - -![BIO通信模型-来源于慕课网](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/3c3aa5599f42817372ce73658387d32a.png) - -### Java同步阻塞模式 - -Java中的阻塞模式BIO,就是在`java.net`包中的Socket套接字的实现,Socket套接字是TCP/UDP等传输层协议的实现。 - -### Java同步阻塞模式编码 -#### 多线程客户端 -为了测试服务端程序,可以先编写一个多线程客户端用于请求测试。 -```java - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; -import java.util.concurrent.CountDownLatch; - -/** - *

- * BIO测试 - * 模拟20个客户端并发请求,服务端则使用单线程。 - * - * @Author niujinpeng - * @Date 2018/10/15 10:50 - */ -public class SocketClient { - public static void main(String[] args) throws InterruptedException { - Integer clientNumber = 20; - CountDownLatch countDownLatch = new CountDownLatch(clientNumber); - - // 分别启动20个客户端 - for (int index = 0; index < clientNumber; index++, countDownLatch.countDown()) { - SocketClientRequestThread client = new SocketClientRequestThread(countDownLatch, index); - new Thread(client).start(); - } - - synchronized (SocketClient.class) { - SocketClient.class.wait(); - } - } -} - -/** - *

- * 客户端,用于模拟请求 - * - * @Author niujinpeng - * @Date 2018/10/15 10:53 - */ -class SocketClientRequestThread implements Runnable { - - private CountDownLatch countDownLatch; - - /** - * 线程的编号 - */ - private Integer clientIndex; - - - public SocketClientRequestThread(CountDownLatch countDownLatch, Integer clientIndex) { - this.countDownLatch = countDownLatch; - this.clientIndex = clientIndex; - } - - @Override - public void run() { - Socket socket = null; - OutputStream clientRequest = null; - InputStream clientResponse = null; - try { - socket = new Socket("localhost", 83); - clientRequest = socket.getOutputStream(); - clientResponse = socket.getInputStream(); - - //等待,直到SocketClientDaemon完成所有线程的启动,然后所有线程一起发送请求 - this.countDownLatch.await(); - - // 发送请求信息 - clientRequest.write(("这是第" + this.clientIndex + "个客户端的请求").getBytes()); - clientRequest.flush(); - - // 等待服务器返回消息 - System.out.println("第" + this.clientIndex + "个客户端请求发送完成,等待服务器响应"); - int maxLen = 1024; - byte[] contentBytes = new byte[maxLen]; - int realLen; - String message = ""; - - // 等待服务端返回,in和out不能cloese - while ((realLen = clientResponse.read(contentBytes, 0, maxLen)) != -1) { - message += new String(contentBytes, 0, realLen); - } - System.out.println("第" + this.clientIndex + "个客户端接受到来自服务器的消息:" + message); - - } catch (IOException e) { - e.printStackTrace(); - } catch (InterruptedException e) { - e.printStackTrace(); - } finally { - try { - if (clientRequest != null) { - clientRequest.close(); - } - if (clientRequest != null) { - clientResponse.close(); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - } -} -``` -#### 单线程服务端 - -因为Java中的Socket就是BIO的模式,因此我们可以很简单的编写一个BIO单线程服务端。 - -SocketServer.java - -```java - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * BIO服务端 - *

- * 单线程阻塞的服务器端 - * - * @Author niujinpeng - * @Date 2018/10/15 11:17 - */ -public class SocketServer { - - public static void main(String[] args) throws IOException { - ServerSocket serverSocket = new ServerSocket(83); - try { - while (true) { - // 阻塞,直到有数据准备完毕 - Socket socket = serverSocket.accept(); - - // 开始收取信息 - InputStream input = socket.getInputStream(); - OutputStream output = socket.getOutputStream(); - Integer sourcePort = socket.getPort(); - int maxLen = 1024 * 2; - byte[] contextBytes = new byte[maxLen]; - - // 阻塞,直到有数据准备完毕 - int realLen = input.read(contextBytes, 0, maxLen); - // 读取信息 - String message = new String(contextBytes, 0, realLen); - - // 输出接收信息 - System.out.println("服务器收到来自端口【" + sourcePort + "】的信息:" + message); - // 响应信息 - output.write("Done!".getBytes()); - - // 关闭 - output.close(); - input.close(); - socket.close(); - - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - if (serverSocket != null) { - serverSocket.close(); - } - } - } -} - -``` - -#### 多线程服务端 -单线程服务器,在处理请求时只能同时处理一条,也就是说如果在请求到来时发现有请求尚未处理完毕,只能等待处理,因此使用`多线程改进`服务端。 - -SocketServerThread.java - -```java - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; - -/** - * BIO服务端 - *

- * 多线程的阻塞的服务端 - *

- * 当然,接收到客户端的socket后,业务的处理过程可以交给一个线程来做。 - * 但还是改变不了socket被一个一个的做accept()的情况。 - * - * @Author niujinpeng - * @Date 2018/10/15 11:17 - */ -public class SocketServerThread implements Runnable { - - /** - * 日志 - */ - private static final Logger logger = LoggerFactory.getLogger(SocketServerThread.class); - - private Socket socket; - - public SocketServerThread(Socket socket) { - this.socket = socket; - } - - public static void main(String[] args) throws Exception { - ServerSocket serverSocket = new ServerSocket(83); - try { - while (true) { - Socket socket = serverSocket.accept(); - //当然业务处理过程可以交给一个线程(这里可以使用线程池),并且线程的创建是很耗资源的。 - //最终改变不了.accept()只能一个一个接受socket的情况,并且被阻塞的情况 - SocketServerThread socketServerThread = new SocketServerThread(socket); - new Thread(socketServerThread).start(); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - } finally { - if (serverSocket != null) { - serverSocket.close(); - } - } - } - - - @Override - public void run() { - InputStream in = null; - OutputStream out = null; - try { - //下面我们收取信息 - in = socket.getInputStream(); - out = socket.getOutputStream(); - Integer sourcePort = socket.getPort(); - int maxLen = 1024; - byte[] contextBytes = new byte[maxLen]; - //使用线程,同样无法解决read方法的阻塞问题, - //也就是说read方法处同样会被阻塞,直到操作系统有数据准备好 - int realLen = in.read(contextBytes, 0, maxLen); - //读取信息 - String message = new String(contextBytes, 0, realLen); - - //下面打印信息 - logger.info("服务器收到来自于端口:" + sourcePort + "的信息:" + message); - - //下面开始发送信息 - out.write("回发响应信息!".getBytes()); - } catch (Exception e) { - logger.error(e.getMessage(), e); - } finally { - //试图关闭 - try { - if (in != null) { - in.close(); - } - if (out != null) { - out.close(); - } - if (this.socket != null) { - this.socket.close(); - } - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - } -} -``` -看起来多线程增加了服务能力,但是很明显多线程改进之后仍有以下`局限性`: - -- 接收和通知处理结果的过程依旧是单线程的。 -- 系统可以创建的线程数量有限。`cat /proc/sys/kernel/threads-max`可以查看可以创建的线程数量。 -- 如果线程较多,CPU需要更多的时间切换,处理真正业务的时间就会变少。 -- 创建线程会消耗较多资源,JVM创建一个线程都会默认分配128KB空间。 -- 多线程也无法解决因为`调用底层系统`的`同步IO`而决定的同步IO机制。 - - -### 同步阻塞模式总结 -BIO模式因为进程的阻塞挂起,不会消耗过多的CPU资源,而且开发难度低,比较适合并发量小的网络应用开发。同时很容易发现因为请求IO会阻塞进程,所以不时候并发量大的应用。如果为每一个请求分配一个线程,系统开销就会过大。 - -同时在Java中,使用了多线程来处理阻塞模式,也无法解决程序在`accept()`和`read()`时候的阻塞问题。因为`accept()`和`read()`的IO模式支持是基于操作系统的,如果操作系统发现没有套接字从指定的端口传送过来,那么`操作系统就会等待`。这样`accept()`和`read()`方法就会一直等待。 - ----- - -GitHub 源码:[https://github.com/niumoo/java-toolbox](https://github.com/niumoo/java-toolbox/tree/master/src/main/java/net/codingme/box/io/bio) - -此文参考文章:[5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO](https://blog.csdn.net/tjiyu/article/details/52959418) -此文参考文章:[架构设计:系统间通信(3)——IO通信模型和JAVA实践 上篇](https://blog.csdn.net/yinwenjie/article/details/48472237) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/io-bon-blocking.md b/docs/mq/io-bon-blocking.md deleted file mode 100644 index 33adbaa..0000000 --- a/docs/mq/io-bon-blocking.md +++ /dev/null @@ -1,311 +0,0 @@ ---- -title: IO通信模型(二)同步非阻塞模式NIO(NonBlocking IO) -date: 2018-10-25 08:08:08 -url: io/io2-nio -tags: - - NIO - - 通信模型 - - 非阻塞IO - - NonBlocking IO -categories: - - 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7da0bf510061ea2cd6b104262bd340f7.jpg) - -### 同步非阻塞模式(NonBlocking IO) - -在非阻塞模式中,发出Socket的`accept()`和`read()`操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个信息。也就是说进程发起一个read操作后,并不需要一直阻塞等待,而是马上就得到了一个结果。 - -如果结果发现数据准备完毕就可以读取数据,然后拷贝到用户内存。如果结果发现数据没有就绪也会返回,进程继续不断的`主动询问`数据的准备情况是非阻塞模式的一个特点。 -![多路复用IO](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/c6cd2412ccdfc0aec38a87379c8800c6.png) -伪代码表示如下: - -```java -{ - while(read(socket, buffer) != SUCCESS){ - } - process(buffer); -} -``` -### Java同步非阻塞模式 - -如上所述,Java的Socket是阻塞模式的典型应用。在发起`accpet()`和`read()`请求之后会持续阻塞,但是Java中提供了`setSoTimeout()`方法设置超时时间,在固定时间内没有得到结果,就会结束本次阻塞,等待进行下一次的阻塞轮询。这是,也就实现了应用层面的非阻塞。 - -Java中Socket中的`setSoTimeout()`方法: -```java -public synchronized void setSoTimeout(int timeout) throws SocketException { - if (isClosed()) - throw new SocketException("Socket is closed"); - if (timeout < 0) - throw new IllegalArgumentException("timeout can't be negative"); - getImpl().setOption(SocketOptions.SO_TIMEOUT, new Integer(timeout)); -} -``` -### Java同步非阻塞模式编码 -通过设置`setSoTimeout()`使阻塞模式的服务端`accpet()`和`read()`优化为非阻塞模式。 -SocketServerNioListenAndRead.java - -```java - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; -import java.net.SocketTimeoutException; - -/** - *

- * 非阻塞IO - 监听非阻塞 - 读取非阻塞 - * - * @Author niujinpeng - * @Date 2018/10/15 14:53 - */ -public class SocketServerNioListenAndRead { - /** - * 日志 - */ - private static final Logger logger = LoggerFactory.getLogger(SocketServerNioListenAndRead.class); - private static Object xWait = new Object(); - - public static void main(String[] args) throws IOException { - ServerSocket serverSocket = null; - - try { - serverSocket = new ServerSocket(83); - serverSocket.setSoTimeout(100); - while (true) { - Socket socket = null; - try { - socket = serverSocket.accept(); - } catch (SocketTimeoutException e) { - synchronized (SocketServerNioListenAndRead.xWait) { - logger.info("没有从底层接收到任务数据报文,等待10ms,,模拟事件X的处理时间"); - SocketServerNioListenAndRead.xWait.wait(10); - } - continue; - } - - InputStream input = socket.getInputStream(); - OutputStream output = socket.getOutputStream(); - Integer sourcePort = socket.getPort(); - int maxLen = 2048; - byte[] contentBytes = new byte[maxLen]; - int realLen; - StringBuffer message = new StringBuffer(); - - // 接收消息非阻塞实现 - socket.setSoTimeout(10); - - BIORead: - while (true) { - try { - // 读取的时候,程序会阻塞,知道系统把网络传过来的数据准备完毕 - while ((realLen = input.read(contentBytes, 0, maxLen)) != -1) { - message.append(new String(contentBytes, 0, realLen)); - /** - * 如果收到over,表示传送完毕 - */ - if (message.toString().endsWith("over")) { - break BIORead; - } - } - } catch (SocketTimeoutException e) { - //=========================================================== - // 执行到这里,说明本次read没有接收到任何数据流 - // 主线程在这里又可以做一些事情,记为Y - //=========================================================== - logger.info("这次没有从底层接收到任务数据报文,等待10毫秒,模拟事件Y的处理时间"); - continue; - } - - } - - // 输出信息 - logger.info("服务器收到来自端口" + sourcePort + "的消息:" + message.toString()); - // 响应 - output.write("Done!".getBytes()); - - output.close(); - input.close(); - socket.close(); - } - } catch (SocketException | InterruptedException e) { - logger.error(e.getMessage(), e); - } finally { - if (serverSocket != null) { - serverSocket.close(); - } - } - } - -} - - -``` -上面的代码可以实现监听和读取数据的非阻塞,但是还是只能一个一个的处理,可以使用多线程`稍微改进`。 -SocketServerNioListenThread.java -```java - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketTimeoutException; - -/** - *

- * 非阻塞IO - 监听非阻塞 - 读取非阻塞 - * 通过加入线程的概念,让socket server能够在应用层面 - * 通过非阻塞的方式同时处理多个socket套接字 - *

- * 此时可以实现非阻塞的IO,但是因为调用了系统底层的阻塞同步IO, - * 因此仍然没有从根本上解决问题 - * - * @Author niujinpeng - * @Date 2018/10/15 15:23 - */ -public class SocketServerNioListenThread { - - private static Object xWait = new Object(); - - private static final Logger LOGGER = LoggerFactory.getLogger(SocketServerNioListenThread.class); - - public static void main(String[] args) throws Exception { - ServerSocket serverSocket = new ServerSocket(83); - serverSocket.setSoTimeout(100); - try { - while (true) { - Socket socket = null; - try { - socket = serverSocket.accept(); - } catch (SocketTimeoutException e1) { - //=========================================================== - // 执行到这里,说明本次accept没有接收到任何TCP连接 - // 主线程在这里就可以做一些事情,记为X - //=========================================================== - synchronized (SocketServerNioListenThread.xWait) { - LOGGER.info("这次没有从底层接收到任何TCP连接,等待10毫秒,模拟事件X的处理时间"); - SocketServerNioListenThread.xWait.wait(10); - } - continue; - } - //当然业务处理过程可以交给一个线程(这里可以使用线程池),并且线程的创建是很耗资源的。 - //最终改变不了.accept()只能一个一个接受socket连接的情况 - SocketServerThread socketServerThread = new SocketServerThread(socket); - new Thread(socketServerThread).start(); - } - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } finally { - if (serverSocket != null) { - serverSocket.close(); - } - } - } -} - -/** - * 当然,接收到客户端的socket后,业务的处理过程可以交给一个线程来做。 - * 但还是改变不了socket被一个一个的做accept()的情况。 - * - * @author niujinpeng - */ -class SocketServerThread implements Runnable { - - /** - * 日志 - */ - private static final Logger LOGGER = LoggerFactory.getLogger(SocketServerThread.class); - - private Socket socket; - - public SocketServerThread(Socket socket) { - this.socket = socket; - } - - @Override - public void run() { - InputStream in = null; - OutputStream out = null; - try { - in = socket.getInputStream(); - out = socket.getOutputStream(); - Integer sourcePort = socket.getPort(); - int maxLen = 2048; - byte[] contextBytes = new byte[maxLen]; - int realLen; - StringBuffer message = new StringBuffer(); - //下面我们收取信息(设置成非阻塞方式,这样read信息的时候,又可以做一些其他事情) - this.socket.setSoTimeout(10); - BIORead: - while (true) { - try { - while ((realLen = in.read(contextBytes, 0, maxLen)) != -1) { - message.append(new String(contextBytes, 0, realLen)); - /* - * 我们假设读取到“over”关键字, - * 表示客户端的所有信息在经过若干次传送后,完成 - * */ - if (message.indexOf("over") != -1) { - break BIORead; - } - } - } catch (SocketTimeoutException e2) { - //=========================================================== - // 执行到这里,说明本次read没有接收到任何数据流 - // 主线程在这里又可以做一些事情,记为Y - //=========================================================== - LOGGER.info("这次没有从底层接收到任务数据报文,等待10毫秒,模拟事件Y的处理时间"); - continue; - } - } - //下面打印信息 - Long threadId = Thread.currentThread().getId(); - LOGGER.info("服务器(线程:" + threadId + ")收到来自于端口:" + sourcePort + "的信息:" + message); - - //下面开始发送信息 - out.write("回发响应信息!".getBytes()); - - //关闭 - out.close(); - in.close(); - this.socket.close(); - } catch (Exception e) { - LOGGER.error(e.getMessage(), e); - } - } -} - -``` - -### 同步非阻塞模式总结 - -用户需要不断地调用,尝试读取数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。 - -开发难度相对于阻塞IO模式较难,适合并发小且不需要及时响应的网络应用开发。 - -GitHub 源码:[https://github.com/niumoo/java-toolbox/](https://github.com/niumoo/java-toolbox/tree/master/src/main/java/net/codingme/box/io/nio) -此文参考文章:[IO复用,AIO,BIO,NIO,同步,异步,阻塞和非阻塞](https://www.cnblogs.com/aspirant/p/6877350.html?utm_source=itdadao&utm_medium=referral) -此文参考文章:[6.2 I/O Models](http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch06lev1sec2.html) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/io-nio-selector.md b/docs/mq/io-nio-selector.md deleted file mode 100644 index 7c136f5..0000000 --- a/docs/mq/io-nio-selector.md +++ /dev/null @@ -1,408 +0,0 @@ ---- -title: IO通信模型(三)多路复用IO -date: 2018-10-27 08:08:08 -url: io/io3-nio -tags: - - IO - - NIO - - 通信模型 - - 多路复用IO -categories: - - 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![mac背包](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/59a331b8fdebaf1dcecab9fe31bb8b80.png) -## 多路复用IO -从`非阻塞同步IO`的介绍中可以发现,为每一个接入创建一个线程在请求很多的情况下不那么适用了,因为这会渐渐耗尽服务器的资源,人们也都意识到了这个 问题,因此终于有人发明了`IO多路复用`。最大的特点就是`不需要开那么多的线程和进程`。 -`多路复用IO`是指使用一个线程来检查多个文件描述符(Socket)的就绪状态,比如调用select和poll函数,传入多个文件描述符,如果有一个文件描述符就绪,则返回,否则阻塞直到超时。得到就绪状态后进行真正的操作可以在同一个线程里执行,也可以启动线程执行(比如使用线程池)。 - -![NIO 通信模型](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/083f9772318bc178ec518d6aaadca09e.png) - -如图,这样在处理多个连接时,可以只需要一个线程监控就绪状态,对就绪的每个连接开一个线程处理就可以了,这样需要的线程数大大减少,减少了内存开销和上下文切换的CPU开销。 - - - 多路复用IO有几个比较重要的概念,下面一一讲解。 - -## 缓冲区Buffer -`Buffer`本质是可以写入可以读取的内存,这块内存被包装成了NIO的Buffer对象,然后为它提供一组用于访问的方法。Java则为`java.nio.Buffer`实现了基本数据类型的`Buffer`。 -![Buffer](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1f344908223446333ce9db0f67bcc38f.png) -所有的Buffer缓冲区都有4个属性,具体解释可以看表格。 - -| 属性 | 描述 | -| -------- | ------------------------------------------------------------ | -| Capacity | 容量,可以容纳的最大数据量,不可变 | -| Limit | 上届,缓冲区当前数据量,Capacity=>Limit | -| Position | 位置,下一个要被读取或者写入的元素的位置,Capacity>=Position | -| Mark | 标记,调用mark()来设置mark=position,再调用reset()设置position=mark | - -这4个属性遵循大小关系: `mark <= position <= limit <= capacity` - -### Buffer的基本用法 - -使用Buffer读写数据一般遵循以下四个步骤: -1. 写入数据到`Buffer`。 -2. 调用`flip()`方法。 -3. 从Buffer中读取数据。 -4. 调用`clear()`方法或者`compact()`方法。 -### Buffer的测试代码 -下面是对于Java中`ByteBuffer`的测试代码: -```java - // 申请一个大小为1024bytes的缓冲buffer - ByteBuffer byteBuffer = ByteBuffer.allocate(1024); - System.out.println("申请到的Buffer:"+byteBuffer); - - // 写入helloworld到buffer - byteBuffer.put("HelloWorld".getBytes()); - System.out.println("写入HelloWorld到Buffer:"+byteBuffer); - - // 切换为读模式 - byteBuffer.flip(); - // 当前Buffer已存放的大小 - int length = byteBuffer.remaining(); - byte[] bytes = new byte[length]; - - // 读取bytes长度的数据 - byteBuffer.get(bytes); - System.out.println("从buffer读取到数据:"+new String(bytes,"UTF-8")); - - // 切换为compact 清空已读取的数据 - byteBuffer.compact(); - System.out.println("读取后的Buffer:"+byteBuffer); -``` -得到如下输出: -```java -申请到的Buffer:java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024] -写入HelloWorld到Buffer:java.nio.HeapByteBuffer[pos=10 lim=1024 cap=1024] -从buffer读取到数据:HelloWorld -读取后的Buffer:java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024] -``` -需要说明的是`flip()`方法将`Buffer`从写模式切换到读模式,`clear()`方法会清空整个缓冲区。`compact()`方法只会清除已经读过的数据。 - -### Buffer的读写模式 -注意读写模式切换时候几个标记位的变化。 -![IO读写模式](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ff580cf428b9f8e3b5f381df31d083f6.png) - -## 通道Channel -通道Channel和流类似,不同的是通道的工作模式可以是全双工。也就是说既可以读取,也可以写入。同时也可以异步的进行读写。`Channel`连接着底层数据与缓冲区`Buffer`。 -同样的,Java中针对不同的情况实现了不同的Channel操作类。常用的有 -1. FileChannel 从文件中读写数据。 -1. DatagramChannel 能通过UDP读写网络中的数据。 -1. SocketChannel 能通过TCP读写网络中的数据。 -1. ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。 - -下面是对于Java中Channel和Buffer的简单演示: -```java - // 申请一个大小为1024bytes的缓冲buffer - ByteBuffer byteBuffer = ByteBuffer.allocate(1024); - // 初始化Channel数据 - FileInputStream fis = new FileInputStream("f:/test.txt"); - FileChannel channel = fis.getChannel(); - System.out.println("Init Channel size:" + channel.size()); - // 从channel中读取数据 - int read = channel.read(byteBuffer); - System.out.println("Read Size :" + read); - System.out.println("byteBuffer:"+byteBuffer); - // 切换到读取模式 - byteBuffer.flip(); - // 输出byteBuffer内容 - System.out.print("print byteBuffer:"); - while (byteBuffer.hasRemaining()){ - System.out.print((char) byteBuffer.get()); - } - byteBuffer.clear(); - System.out.println(byteBuffer); - fis.close(); - -``` -输出信息如下: -```java -Init Channel size:10 -Read Size :10 -byteBuffer:java.nio.HeapByteBuffer[pos=10 lim=1024 cap=1024] -print byteBuffer:helloworld -``` -需要注意的是,在读取之前一定要调用`flip()`切换到读取模式。 -## 选择器Selector -Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。我们也可以称`Selector`为轮询代理器,事件订阅器或者channel容器管理器。 -应用程序将向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。 - -关于IO事件,我们可以在`SelectionKey`类中找到几个常用事件: - -1. OP_READ 可以读取 -2. OP_WRITE 可以写入 -3. OP_CONNECT 已经连接 -4. OP_ACCEPT 可以接受 - -值得注意的是,在程序中都是通过不断的轮询已经注册的Channel,根据检查注册时的感兴趣事件是否已经就绪来决定是否可以进行后续操作。同时`Selector`也有几个经常使用的方法。 - -1. select() 阻塞到至少有一个通道在你注册的事件上就绪了。 - -2. select(long timeout) 最长会阻塞timeout毫秒 - -3. selectNow() 会阻塞,不管什么通道就绪都立刻返回 - -4. selectedKeys() 返回就绪的通道 - -下面是一个对Java中Selector编写服务端的简单使用测试(客户端不在此编写了,如有需要,可以查看[IO通信模型(一)同步阻塞模式BIO(Blocking IO)](https://www.wdbyte.com/2018/10/io/io1-bio/)中的客户端代码): - -```java - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.Iterator; -import java.util.Set; - -/** - *

- * NIO-Selector - * 选择器的使用测试 - * Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读 - * 写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接 - * 。我们也可以称Selector为轮询代理器,事件订阅器或者channel容器管理器。 - * 应用程序将向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些 - * IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。 - * - * @Author niujinpeng - * @Date 2018/10/26 15:31 - */ -public class NioSelector { - - public static void main(String[] args) throws IOException { - // 获取channel - ServerSocketChannel channel = ServerSocketChannel.open(); - // channel是否阻塞 - channel.configureBlocking(false); - // 监听88端口 - ServerSocket socket = channel.socket(); - socket.bind(new InetSocketAddress(83)); - - - // 创建选择器Selector - Selector selector = Selector.open(); - // 像选择器中注册channel - channel.register(selector, SelectionKey.OP_ACCEPT); - - while (true) { - // 阻塞到有一个就绪 - int readyChannel = selector.select(); - if (readyChannel == 0) { - continue; - } - Set selectionKeys = selector.selectedKeys(); - Iterator iterator = selectionKeys.iterator(); - while (iterator.hasNext()) { - SelectionKey selectionKey = iterator.next(); - iterator.remove(); - // 是否可以接受 - if (selectionKey.isAcceptable()) { - System.out.println("准备就绪"); - SelectableChannel selectableChannel = selectionKey.channel(); - ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectableChannel; - SocketChannel socketChannel = serverSocketChannel.accept(); - socketChannel.configureBlocking(false); - // 注册感兴趣事件-读取 - socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(2048)); - } else if (selectionKey.isConnectable()) { - System.out.println("已连接"); - - } else if (selectionKey.isReadable()) { - System.out.println("可以读取"); - - } else if (selectionKey.isWritable()) { - System.out.println("可以写入"); - - } - } - } - } -} - -``` - -## Java NIO编程 - -到这里,已经对`多路复用IO`有了一个基本的认识了,可以结合上面的三个概念就行多路复用IO编程了,下面演示使用Java语言编写一个`多路复用IO`服务端。 -NioSocketServer.java - -```java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.ByteBuffer; -import java.nio.channels.*; -import java.util.Iterator; - -/** - *

- * 使用Java NIO框架,实现一个支持多路复用IO的服务器端 - * - * @Author niujinpeng - * @Date 2018/10/16 0:53 - */ -public class NioSocketServer { - /** - * 日志 - */ - private static final Logger LOGGER = LoggerFactory.getLogger(NioSocketServer.class); - - public static void main(String[] args) throws IOException { - ServerSocketChannel serverChannel = ServerSocketChannel.open(); - // 是否阻塞 - serverChannel.configureBlocking(false); - ServerSocket serverSocket = serverChannel.socket(); - serverSocket.setReuseAddress(true); - serverSocket.bind(new InetSocketAddress(83)); - - Selector selector = Selector.open(); - // 服务器通道只能注册SelectionKey.OP_ACCEPT事件 - serverChannel.register(selector, SelectionKey.OP_ACCEPT); - - while (true) { - // java程序对多路复用IO的支持也包括了阻塞模式 和非阻塞模式两种。 - if (selector.select(100) == 0) { - //LOGGER.info("本次询问selector没有获取到任何准备好的事件"); - continue; - } - - // 询问系统,所有获取到的事件类型 - Iterator selectionKeys = selector.selectedKeys().iterator(); - while (selectionKeys.hasNext()) { - SelectionKey readKey = selectionKeys.next(); - // 上面获取到的readKey要移除,不然会一直存在selector.selectedKeys()的集合之中 - selectionKeys.remove(); - - SelectableChannel selectableChannel = readKey.channel(); - if (readKey.isValid() && readKey.isAcceptable()) { - LOGGER.info("--------------channel通道已经准备完毕-------------"); - /* - * 当server socket channel通道已经准备好,就可以从server socket channel中获取socketchannel了 - * 拿到socket channel后,要做的事情就是马上到selector注册这个socket channel感兴趣的事情。 - * 否则无法监听到这个socket channel到达的数据 - * */ - ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectableChannel; - SocketChannel socketChannel = serverSocketChannel.accept(); - registerSocketChannel(socketChannel, selector); - } else if (readKey.isValid() && readKey.isConnectable()) { - LOGGER.info("--------------socket channel 建立连接-------------"); - } else if (readKey.isValid() && readKey.isReadable()) { - LOGGER.info("--------------socket channel 数据准备完成,可以开始读取-------------"); - try { - readSocketChannel(readKey); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - } - } - } - } - - } - - /** - * 在server socket channel接收到/准备好 一个新的 TCP连接后。 - * 就会向程序返回一个新的socketChannel。
- * 但是这个新的socket channel并没有在selector“选择器/代理器”中注册, - * 所以程序还没法通过selector通知这个socket channel的事件。 - * 于是我们拿到新的socket channel后,要做的第一个事情就是到selector“选择器/代理器”中注册这个 - * socket channel感兴趣的事件 - * - * @param socketChannel - * @param selector - * @throws Exception - */ - private static void registerSocketChannel(SocketChannel socketChannel, Selector selector) { - // 是否阻塞 - try { - socketChannel.configureBlocking(false); - // 读模式只能读,写模式可以同时读 - // socket通道可以且只可以注册三种事件SelectionKey.OP_READ | SelectionKey.OP_WRITE | SelectionKey.OP_CONNECT - socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(2048)); - } catch (IOException e) { - LOGGER.info(e.toString(), e); - } - - } - - private static void readSocketChannel(SelectionKey readKey) throws Exception { - SocketChannel clientSocketChannel = (SocketChannel) readKey.channel(); - //获取客户端使用的端口 - InetSocketAddress sourceSocketAddress = (InetSocketAddress) clientSocketChannel.getRemoteAddress(); - int sourcePort = sourceSocketAddress.getPort(); - - // 拿到这个socket channel使用的缓存区,准备读取数据 - // 解缓存区的用法概念,实际上重要的就是三个元素capacity,position和limit。 - ByteBuffer contextBytes = (ByteBuffer) readKey.attachment(); - // 通道的数据写入到【缓存区】 - // 由于之前设置了ByteBuffer的大小为2048 byte,所以可以存在写入不完的情况,需要调整 - int realLen = -1; - try { - realLen = clientSocketChannel.read(contextBytes); - } catch (Exception e) { - LOGGER.error(e.getMessage()); - clientSocketChannel.close(); - return; - } - - // 如果缓存中没有数据 - if (realLen == -1) { - LOGGER.warn("--------------缓存中没有数据-------------"); - return; - } - - // 将缓存区读写状态模式进行切换 - contextBytes.flip(); - // 处理编码问题 - byte[] messageBytes = contextBytes.array(); - String messageEncode = new String(messageBytes, "UTF-8"); - String message = URLDecoder.decode(messageEncode, "UTF-8"); - - // 接受到了"over"则清空buffer,并响应,否则不清空缓存,并还原Buffer写状态 - if (message.indexOf("over") != -1) { - //清空已经读取的缓存,并从新切换为写状态(这里要注意clear()和capacity()两个方法的区别) - contextBytes.clear(); - LOGGER.info("端口【" + sourcePort + "】客户端发来的信息:" + message); - LOGGER.info("端口【" + sourcePort + "】客户端消息发送完毕"); - // 响应 - ByteBuffer sendBuffer = ByteBuffer.wrap(URLEncoder.encode("Done!", "UTF-8").getBytes()); - clientSocketChannel.write(sendBuffer); - clientSocketChannel.close(); - } else { - LOGGER.info("端口【" + sourcePort + "】客户端发来的信息还未完毕,继续接收"); - // limit和capacity的值一致,position的位置是realLen的位置 - contextBytes.position(realLen); - contextBytes.limit(contextBytes.capacity()); - } - } -} -``` - -## 多路复用IO优缺点 - -- 不需要使用多线程进行IO处理了 -- 同一个端口可以处理多种协议 -- 多路复用IO具有操作系统级别的优化 -- 其实底层还都是同步IO - -文章代码已经上传GitHub:[https://github.com/niumoo/java-toolbox/](https://github.com/niumoo/java-toolbox/tree/master/src/main/java/net/codingme/box/io/jdknio) - -<完> - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/mq-activemq.md b/docs/mq/mq-activemq.md deleted file mode 100644 index d82311c..0000000 --- a/docs/mq/mq-activemq.md +++ /dev/null @@ -1,605 +0,0 @@ ---- -title: 消息队列中间件(二)使用 ActiveMQ -date: 2018-12-01 16:59:48 -url: io/mq-activemq -tags: - - 中间件 - - 消息队列 - - ActiveMq -categories: - - 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![ActiveMQ-log](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/fc60f07d8257056ab302149f1ebc6290.png) - -## ActiveMQ 介绍 - -Active MQ 是由 Apache 出品的一款流行的功能强大的开源消息中间件,它速度快,支持跨语言的客户端,具有易于使用的企业集成模式和许多的高级功能,同时完全支持 [JSM1.1](https://www.oracle.com/technetwork/java/jms/index.html) 和 J2EE1.4 。 - -- 官方下载地址: [http://activemq.apache.org/download.html](http://activemq.apache.org/download.html) - -- 官方安装教程: [http://activemq.apache.org/getting-started.html](http://activemq.apache.org/getting-started.html#GettingStarted-InstallationProcedureforWindows) - -- 默认管理页面:[http://127.0.0.1:8161/admin/](http://127.0.0.1:8161/admin/) - - 默认用户名和密码为admin / admin。您可以在conf / jetty-real.properties文件中进行配置。 - -- 默认服务端口:61616 - - -## ActiveMQ 特点 - -- 支持Java,C,C ++,C#,Ruby,Perl,Python,PHP等各种跨语言客户端和协议,如 OpenWire , Stomp , AMQP , MQTT. -- 完全支持JMS 1.1和 J2EE 1.4,支持瞬态,持久,事务和XA消息传递。 -- 对 Spring 框架的支持以便ActiveMQ可以轻松嵌入到Spring应用程序中。 -- 通过了常见的 J2EE 服务器测试,如 TomEE,Geronimo,JBoss,GlassFish 和 WebLogic 。 -- 连接方式的多样化,ActiveMQ 提供了多种连接模式,例如 in-VM、TCP、SSL、NIO、UDP、多播、JGroups、JXTA。 -- 可以通过使用 JDBC 和 journal 实现消息的快速持久化。 -- 专为高性能群集,客户端 - 服务器,点对点通信而设计。 -- 提供与语言无关的 REST API。 -- 支持 Ajax 方式调用 ActiveMQ。 -- ActiveMQ 可以轻松地与 CXF、Axis 等 Web Service 技术整合,以提供可靠的消息传递。 -- 可用作为内存中的 JMS 提供者,非常适合 JMS 单元测试。 - - - -## ActiveMQ 消息 - -1. 点对点队列模式 - 消息到达消息系统,被保留在消息队列中,然后由一个或者多个消费者消费队列中的消息,一个消息只能被一个消费者消费,然后就会被移除。例如订单处理系统。 -2. 发布-订阅模式 - 消息发送时指定主题(或者说通道),消息被保留在指定的主题中,消费者可以订阅多个主题,并使用主题中的所有的消息,例如现实中的电视与电视频道。所有客户端包括发布者和订阅者,主题中的消息可以被所有的订阅者消费,消费者只能消费订阅之后发送到主题中的消息。 - -## ActiveMQ 概念 - -- Broker,消息代理,表示消息队列服务器实体,接受客户端连接,提供消息通信的核心服务。 -- Producer,消息生产者,业务的发起方,负责生产消息并传输给 Broker 。 -- Consumer,消息消费者,业务的处理方,负责从 Broker 获取消息并进行业务逻辑处理。 -- Topic,主题,发布订阅模式下的消息统一汇集地,不同生产者向 Topic 发送消息,由 Broker 分发到不同的订阅者,实现消息的广播。 -- Queue,队列,点对点模式下特定生产者向特定队列发送消息,消费者订阅特定队列接收消息并进行业务逻辑处理。 -- Message,消息体,根据不同通信协议定义的固定格式进行编码的数据包,来封装业务 数据,实现消息的传输。 - - - -## ActiveMQ 工程实例 - -下面是使用 ActiveMQ 的队列模式和发布-订阅模式的 Java 代码示例。 - -### POM 依赖 - -```xml - - - org.apache.activemq - activemq-all - 5.15.5 - -``` - -### 队列模式消费者 - -```java -import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - -/** - *

- * 消息消费者,用于消费消息 - * - * @Author niujinpeng - * @Date 2018/9/4 23:45 - */ -public class AppConsumer { - - private static final String url = "tcp://127.0.0.1:61616"; - private static final String queueName = "queue-test"; - - public static void main(String[] args) throws JMSException { - // 1.创建ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(); - // 2.创建Connection - Connection connection = connectionFactory.createConnection(); - // 3.启动连接 - connection.start(); - - // 4.创建会话,false,不使用事务,自动应答模式 - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // 5.创建一个目标 - Destination destination = session.createQueue(queueName); - // 6.创建消费者 - MessageConsumer consumer = session.createConsumer(destination); - - // 7.创建一个监听器 - consumer.setMessageListener(new MessageListener() { - public void onMessage(Message message) { - TextMessage textMessage = (TextMessage) message; - try { - System.out.println("接收消息:" + textMessage.getText()); - } catch (JMSException e) { - e.printStackTrace(); - } - } - }); - - // 8.关闭连接 - //connection.close(); - } -} -``` - -### 队列模式生产者 - -```java -import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - -/** - *

- * 消息提供者,用于向消息中间件发送消息 - * - * @Author niujinpeng - * @Date 2018/9/4 23:28 - */ -public class AppProducer { - - private static final String url = "tcp://127.0.0.1:61616"; - private static final String queueName = "queue-test"; - - public static void main(String[] args) throws JMSException { - // 1.创建ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(); - // 2.创建Connection - Connection connection = connectionFactory.createConnection(); - // 3.启动连接 - connection.start(); - - // 4.创建会话,false,不使用事务,自动应答模式 - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // 5.创建一个目标 - Destination destination = session.createQueue(queueName); - // 6.创建生产者 - MessageProducer producer = session.createProducer(destination); - - // 7.创建消息并发送 - for (int i = 0; i < 10; i++) { - // 创建消息 - TextMessage textMessage = session.createTextMessage("textMessage" + i); - // 发布消息 - producer.send(textMessage); - System.out.println("发送消息:" + textMessage.getText()); - } - - // 8.关闭连接 - connection.close(); - - } -} - -``` - -### 发布订阅模式生产者 - -```java -import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - -/** - *

- * 主题模式 - * 消息消费者,用于消费消息 - * - * @Author niujinpeng - * @Date 2018/9/4 23:45 - */ -public class AppConsumer { - - private static final String url = "tcp://127.0.0.1:61616"; - private static final String topicName = "topic-test"; - - public static void main(String[] args) throws JMSException { - // 1.创建ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(); - // 2.创建Connection - Connection connection = connectionFactory.createConnection(); - // 3.启动连接 - connection.start(); - - // 4.创建会话,false,不使用事务,自动应答模式 - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // 5.创建一个目标 - Destination destination = session.createTopic(topicName); - // 6.创建消费者 - MessageConsumer consumer = session.createConsumer(destination); - - // 7.创建一个监听器 - consumer.setMessageListener(new MessageListener() { - public void onMessage(Message message) { - TextMessage textMessage = (TextMessage) message; - try { - System.out.println("接收消息:" + textMessage.getText()); - } catch (JMSException e) { - e.printStackTrace(); - } - } - }); - - // 8.关闭连接 - //connection.close(); - } -} - -``` - -### 发布订阅模式生产者 - -```java -import org.apache.activemq.ActiveMQConnectionFactory; -import javax.jms.*; - -/** - *

- * 主题模式 - * 消息提供者,用于向消息中间件发送消息 - * - * @Author niujinpeng - * @Date 2018/9/4 23:28 - */ -public class AppProducer { - - private static final String url = "tcp://127.0.0.1:61616"; - private static final String topicName = "topic-test"; - - public static void main(String[] args) throws JMSException { - // 1.创建ConnectionFactory - ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(); - // 2.创建Connection - Connection connection = connectionFactory.createConnection(); - // 3.启动连接 - connection.start(); - - // 4.创建会话,false,不使用事务,自动应答模式 - Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); - // 5.创建一个目标 - Destination destination = session.createTopic(topicName); - // 6.创建生产者 - MessageProducer producer = session.createProducer(destination); - - // 7.创建消息并发送 - for (int i = 0; i < 10; i++) { - // 创建消息 - TextMessage textMessage = session.createTextMessage("textMessage" + i); - // 发布消息 - producer.send(textMessage); - System.out.println("发送消息:" + textMessage.getText()); - } - - // 8.关闭连接 - connection.close(); - - } -} - -``` -**GitHub源码:**[https://github.com/niumoo/message-queue](https://github.com/niumoo/message-queue/tree/master/mq-activemq) - - - -## Spring 整合 ActiveMQ - -在 Spring 中配置 Active MQ 就像Spring 整合其他功能一样,我们需要在 XML 配置中配置几个关键的实例即可。在 Active MQ 中有几个对象的实例是至关重要的,如 Active MQ jms 连接工厂,为了减少连接断开性能时间消耗的 jms 连接池以及生产者消费者等。 - -下面是一些详细说明。 - -- ConnectionFactory 用于管理连接的连接工厂(Spring提供)。 - - 一个 Spring 为我们提供的连接池。 - - JmsTemplate 每次发送都会重新创建连接,会话和 Productor。 - - Spring 中提供了SingleConnectionFactory 和CachingConnectionFactory(增加了缓存功能)。 -- JmsTemplate 是用于发送和接收消息的模板类。 - - 是spring提供的,只需要向Spring 容器内注册这个类就可以使用 JmsTemplate 方便的操作jms。 - - JmsTemplate 类是线程安全的,可以在整个应用范围使用。 -- MessageListerner 消息监听器 - - 使用一个onMessage方法,该方法只接收一个Message参数。 - -### POM 依赖 - -```xml - - 5.0.4.RELEASE - - - - - junit - junit - 4.11 - test - - - - org.springframework - spring-context - ${spring.version} - - - - org.springframework - spring-jms - 5.1.1.RELEASE - - - - org.springframework - spring-test - ${spring.version} - - - - - javax.jms - javax.jms-api - 2.0.1 - - - - org.apache.activemq - activemq-core - 5.7.0 - - - spring-context - org.springframework - - - org.apache.geronimo.specs - geronimo-jms_1.1_spec - - - - -``` - -### XML 配置 - -#### XML 公共配置 - -为了份文件配置方便管理,下面是提取出来的公共配置,为了在独立配置生产者和消费者 XML文件时引入,当然也可以直接把生产者和消费者以及所有的 XML bean 配置在一个文件里。 - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - -``` - -#### XML 消费者 - -消费者主要是一个消息监听器,监听指定的队列或者主题的消息信息,来有消息时调用回调监听处理方法。这里我注释掉了监听的队列模式,指定了主题模式。 - -```xml - - - - - - - - - - - - - - - - - - - - -``` - -#### XML 生产者 - -生成者的配置主要是使用 spring jms 模版对象,创建生产者实例用于生产消息。 - -```xml - - - - - - - - - - - - - - -``` - -### 生产者编写 - -**1. 定义接口** - -```java -package net.codingme.jms.producer; - -/** - *

- * - * @Author niujinpeng - * @Date 2018/11/2518:19 - */ -public interface ProducerService { - public void sendMessage(String message); -} - -``` - -**2. 主题模式生产者** - -```java -package net.codingme.jms.producer; - - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.jms.core.JmsTemplate; -import org.springframework.jms.core.MessageCreator; - -import javax.annotation.Resource; -import javax.jms.*; - - -/** - *

- * - * @Author niujinpeng - * @Date 2018/11/25 19:24 - */ -public class ProducerServiceImpl implements ProducerService { - - @Autowired - JmsTemplate jmsTemplate; - /** - * 主题模式 - */ - @Resource(name = "topicDestination") - Destination destination; - - @Override - public void sendMessage(String message) { - // 使用jmsTemplate发送消息 - jmsTemplate.send(destination, new MessageCreator() { - // 创建消息 - @Override - public Message createMessage(Session session) throws JMSException { - TextMessage textMessage = session.createTextMessage(message); - return textMessage; - } - }); - System.out.println("发送消息:" + message); - - } -} -``` - -**3. Spring 启动 生产者** - -```java -package net.codingme.jms.producer; - -import org.springframework.context.support.ClassPathXmlApplicationContext; - -/** - *

- * 启动器 - * - * @Author niujinpeng - * @Date 2018/11/25 21:48 - */ -public class AppProducer { - - public static void main(String[] args) { - // 装载配置文件 - ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:producer.xml"); - ProducerService service = context.getBean(ProducerService.class); - - for (int i = 0; i < 10; i++) { - service.sendMessage("test" + i); - } - context.close(); - } - -} -``` - -### 消费者编写 - -Spring启动和生产者类似。下面是消费者监听器的实现。 - -```java -package net.codingme.jms.consumer; - -import javax.jms.JMSException; -import javax.jms.Message; -import javax.jms.MessageListener; -import javax.jms.TextMessage; - -/** - *

- * 消息监听器 - * - * @Author niujinpeng - * @Date 2018/11/25 22:28 - */ -public class ConsumerMessageListener implements MessageListener { - @Override - public void onMessage(Message message) { - TextMessage textMessage = (TextMessage) message; - try { - System.out.println("接收消息:" + textMessage.getText()); - } catch (JMSException e) { - e.printStackTrace(); - } - } -} -``` - - - -### 运行测试 - -首先主题模式下启动两个消费者,使用生产者推送10条消息。 - -![测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/64b0c8878167331cbcce3fe60dfb084a.png) - -在每个消费者下面都可以看到推送的完整消息。 - -![测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/fb01f346bfa248e42bbe9af183697762.png) - -文中代码已经上传到GitHub:[https://github.com/niumoo/message-queue](https://github.com/niumoo/message-queue) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/mq-introduction.md b/docs/mq/mq-introduction.md deleted file mode 100644 index 817e4b4..0000000 --- a/docs/mq/mq-introduction.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: 消息队列中间件(一)介绍 -date: 2018-11-23 16:59:48 -url: io/mq-introduction -tags: -- 中间件 -- 消息队列 -categories: -- 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/09ec812b08047f011dcca4e8e6809e35.jpg) - -## 消息队列介绍 -消息队列中间件是大型系统中的重要组件,已经逐渐成为企业系统内部通信的核心手段。它具有松耦合、异步消息、流量削峰、可靠投递、广播、流量控制、最终一致性等一系列功能,已经成为异步RPC的主要手段之一。 -目前常见的消息中间件有ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RocketMQ等。 - - -## 消息队列应用场景 -消息队列在实际中常见的应用场景有应用解耦、异步处理、流量错峰与流控、日志处理等等。 -### 应用解耦 -消息中间件顾名思义是用于消息存放的中间件。拿支付订单流程举例,在没有中间件的情况下,流程大致如下: -- 用户支付订单,更新订单状态 -- 调用库存服务,完成响应功能 -- 调用积分服务,完成响应功能 -- 调用短信服务,发送短信通知 - -这个过程是顺序执行的,如果库存和积分或者短信服务没有及时响应,或者短信服务处理堵塞,客户端用户收到响应的时间将会延长,体验变差。 -其实我们知道对于订单流程,只有订单处理才是核心服务,其他依赖系统不是那么重要,可以通知到即可。所以可以使用消息中间件,我们在处理完毕订单之后放入中间件立刻返回,然后后续服务从中间件中拿到数据进行后续的处理。 - -### 异步处理 -异步处理是使用消息中间件的一个重要功能,拿用户注册来说,如果没有消息中间件,流程大致如下: -- 提交注册信息,保存注册信息① -- 发送注册通知邮件② -- 发送短信验证码③ - -这个过程是顺序的,很明显在发送邮件或短信时候有可能因为网络等原因发送有一定延迟,这时候响应时间变长。时间为①+②+③。 -在不使用中间件的情况下我们可以稍微改进,可以在注册信息记录完毕之后同时调用发送通知邮件和发送短信验证码的程序。时间为①+(max(②,③))。虽然改进,但是因为使用了并行处理,由于CPU的并行处理能力有限,瓶颈很快就会到来。 -可以继续改进,注册信息记录完毕之后写入中间件,立即返回。短信服务和邮件服务从中间件中取出信息发送通知,时间为①+写入中间件时间。 - -### 流量错峰 -在类似于秒杀这样的场景中会在某个时间流量突增,大量的请求同时到达服务端,无疑对后端的压力会大大增加,如果都等着处理完成可能会堵死后续的请求。这时候可以使用消息中间件把需要处理的信息先存储在中间件,也可以控制活动的参与人数。在后续服务程序有能力时再拿出信息进行处理,就可以削平流量峰值。保证处理程序的正常运行。 - -### 日志处理 -在生产环境中,为了监控日志,会有大量的日志需要传输存储检索,现在比较有名的日志处理架构有ELK,在分布式应用中由于日志的数量级越来越大,存储起来对速度的要求也越快越快。这时候需要使用中间件解决大量日志传输的问题,比如Kafka。 -下图是一个常见的日志处理架构: -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/09ec812b08047f011dcca4e8e6809e35.jpg) -1. Kafka:接收用户日志的消息队列。 - -2. Logstash:做日志解析,统一成JSON输出给Elasticsearch。 - -3. Elasticsearch:实时日志分析服务的核心技术,一个schemaless,实时的数据存储服务,通过index组织数据,兼具强大的搜索和统计功能。 - -4. Kibana:基于Elasticsearch的数据可视化组件,超强的数据可视化能力是众多公司选择ELK stack的重要原因。 - - -## JMS消息服务 - -JMS是Java消息服务(Java Message Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。Java消息服务是一个与具体平台无关的API,绝大多数MOM提供商都对JMS提供支持。ActiveMQ就是JMS接口的实现。 - -### JMS消息模式 -大部分的消息队列都有两种通信模式。 -- 点对点(Point-to-Point Messaging Domain) -- 发布-订阅(Publish/Subscribe Messaging Domain) - -JMS也不例外的定义了这两种消息发送模型的规范,但是并没有给予实现,实现JMS接口的消息中间件(MOM)称为JMS Provider。 - -**点对点** -消息到达消息系统,被保留在消息队列中,然后由一个或者多个消费者消费队列中的消息,一个消息只能被一个消费者消费,然后就会被移除。例如订单处理系统。 -**发布-订阅** -消息发送时指定主题(或者说通道),消息被保留在指定的主题中,消费者可以订阅多个主题,并使用主题中的所有的消息,例如现实中的电视与电视频道。所有客户端包括发布者和订阅者,主题中的消息可以被所有的订阅者消费,消费者只能消费订阅之后发送到主题中的消息。 - -### JMS编码接口 -- ConnectionFactory 用于创建连接到消费中间件的连接工厂 -- Connection 代表了应用程序和消息服务器之间通信的线路 -- Destination 指消息发布和接收的地点,包括队列和主题 -- Session 表示一个单线程的上下文,用于发送和接收消息 -- MessageConsumer 由会话创建,用于接收发送到目标的消息 -- MessageProducer 由会话创建,用于发送消息到目标 -- Message 是消费者在生产者之间传送的对象,消息头,一组消息属性,消息体 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/mq/mq-kafka-introduction.md b/docs/mq/mq-kafka-introduction.md deleted file mode 100644 index 38caa6f..0000000 --- a/docs/mq/mq-kafka-introduction.md +++ /dev/null @@ -1,337 +0,0 @@ ---- -title: 消息队列中间件(三)Kafka 入门指南 -date: 2018-12-02 20:59:48 -url: io/mq-kafka-introduction -tags: -- 中间件 -- 消息队列 -- Kafka -categories: -- 中间件通信 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Apache Kafka](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/c4725e4c2f3e90dc9908e6d0b974f7cc.png) - -## Kafka 来源 - -`Kafka`的前身是由`LinkedIn`开源的一款产品,2011年初开始开源,加入了 Apache 基金会,2012年从 Apache Incubator 毕业变成了 Apache 顶级开源项目。同时LinkedIn还有许多著名的开源产品。如: - -- 分布式数据同步系统`Databus` -- 高性能计算引擎`Cubert` -- Java异步处理框架`ParSeq` -- `Kafka`流处理平台 - - - -## Kafka 介绍 - -Kafka 用于构建实时数据管道和流应用程序。它具有水平可扩展性,容错性,快速性,并在数千家公司的生产环境中运行。 - -从官方我们可以知道`ApacheKafka`是*一个分布式流媒体平台*。这到底是什么意思呢? - -流媒体平台有三个关键功能: - -- 发布和订阅记录数据流,类似于消息队列或企业消息传递系统。 -- 有容错能力的可以持久化的存储数据流。 -- 记录发生时可以进行流处理。 - -Kafka 通常用于两大类应用: - -- 构建可在系统或应用程序之间可靠获取数据的实时流数据管道 -- 构建转换或响应数据流的实时流处理 - -## Kafka 基本概念 - -- **Producer** - 消息和数据的生产者,向 Kafka 的一个 Topic 发布消息的进程/代码/服务。 -- **Consumer **- 消息和数据的消费者,订阅数据(Topic)并且处理其发布的消息的进程/代码/服务。 -- **Consumer Group** - 逻辑概念,对于同一个 Topic,会广播不同的 Group,一个Group中,只有一个consumer 可以消费该消息。 -- **Broker** - 物理概念,Kafka 集群中的每个 Kafka 节点。 -- **Topic** - 逻辑概念,Kafka消息的类别,对数据进行区分,隔离。 -- **Partition** - 物理概念,分片,Kafka 下数据存储的基本单元,一个 Topic 数据,会被分散存储到多个Partition,每一个Partition是有序的。 -- **Replication **- 副本,同一个 Partition 可能会有多个 Replica ,多个 Replica 之间数据是一样的。 -- **Replication Leader** - 一个 Partition 的多个 Replica 上,需要一个 Leade r负责该 Partition 上与 Produce 和 Consumer 交互 -- **ReplicaManager** - 负责管理当前的 broker 所有分区和副本的信息,处理 KafkaController 发起的一些请求,副本状态的切换,添加/读取消息等。 - - - -### 概念的延伸 - -Partition -- 每一个Topic被切分为多个Partitions -- 消费者数据要小于等于Partition的数量 -- Broker Group中的每一个Broker保存Topic的一个或多个Partitions -- Consumer Group中的仅有一个Consumer读取Topic的一个或多个Partions,并且是唯一的Consumer。 - -Replication - - 当集群中有Broker挂掉的时候,系统可以主动的使用Replicas提供服务。 - - 系统默认设置每一个Topic的Replication的系数为1,可以在创建Topic的时候单独设置。 - -Replication特点 - - Replication的基本单位是Topic的Partition。 - - 所有的读和写都从Leader进,Followers只是作为备份。 - - Follower必须能够及时的复制Leader的数据 - - 增加容错性与可扩展性。 - - -## Kafka 消息结构 -在 Kafka2.0 中的消息结构如下(整理自官网)。 - -> baseOffset: int64 - 用于记录Kafka这个消息所处的偏移位置 -> batchLength: int32 - 用于记录整个消息的长度 -> partitionLeaderEpoch: int32 -> magic: int8 (current magic value is 2) - 一个固定值,用于快速判断是否是Kafka消息 -> crc: int32 - 用于校验信息的完整性 -> attributes: int16 - 当前消息的一些属性 -> > bit 0~2: -> > > 0: no compression -> > > 1: gzip -> > > 2: snappy -> > > 3: lz4 -> > -> > bit 3: timestampType -> > ​ bit 4: isTransactional (0 means not transactional) -> > ​ bit 5: isControlBatch (0 means not a control batch) -> > ​ bit 6~15: unused -> -> lastOffsetDelta: int32 -> firstTimestamp : int64 -> maxTimestamp: int64 -> producerId: int64 -> producerEpoch: int16 -> baseSequence: int32 -> records: -> > length: varint -> > attributes: int8 -> > > bit 0~7: unused -> > -> > timestampDelta: varint -> > offsetDelta: varint -> > keyLength: varint -> > key: byte[] -> > valueLen: varint -> > value: byte[] -> > Headers => [Header] -> > >headerKeyLength: varint -> > >headerKey: String -> > >headerValueLength: varint -> > >Value: byte[] - -关于消息结构的一些释义。 -- Offset -用于记录Kafka这个消息所处的偏移位置 -- Length - 用于记录整个消息的长度 -- CRC32 - 用于校验信息的完整性 -- Magic - 一个固定值,用于快速判断是否是Kafka消息 -- Attributes - 当前消息的一些属性 -- Timestamp - 消息的时间戳 -- Key Length - key的长度 -- Key - Key的具体值 -- Value Length - 值的长度 -- Value - 具体的消息值 - - - -## Kafka 优点 - -1. 分布式 - Kafka是分布式的,多分区,多副本的和多订阅者的,基于Zookeeper调度。 -2. 持久性和扩展性 - Kafka使用分布式提交日志,这意味着消息会尽可能快地保留在磁盘上,因此它是持久的。同时具有一定的容错性,Kafka支持在线的水平扩展,消息的自平衡。 -3. 高性能 - Kafka对于发布和订阅消息都具有高吞吐量。 即使存储了许多TB的消息,它也保持稳定的性能。且延迟低,适用高并发。时间复杂的为o(1)。 - -## Kafka 应用 -1. 用于聚合分布式应用程序中的消息。进行操作监控。 -2. 用于跨组织的从多个服务收集日志,然后提供给多个服务器,解决日志聚合问题。 -3. 用于流处理,如Storm和Spark Streaming,从kafka中读取数据,然后处理在写入kafka供应用使用。 - - - -## Kafka 安装 -### 安装 Jdk -具体步骤此处不说。 - -### 安装 Kafka -直接[官方网站](http://kafka.apache.org/downloads)下载对应系统的版本解压即可。 -由于Kafka对于windows和Unix平台的控制脚本是不同的,因此如果是windows平台,要使用`bin\windows\`而不是`bin/`,并将脚本扩展名更改为`.bat`。以下命令是基于Unix平台的使用。 - -```shell -# 解压 -tar -xzf kafka_2.11-2.0.0.tgz -# 启动Zookeeper -bin/zookeeper-server-start.sh config/zookeeper.properties -# 启动Kafka -bin/kafka-server-start.sh config/server.properties -# 或者后台启动 -bin/kafka-server-start.sh config/server.properties & -``` - -让我们创建一个名为“test”的主题,它只包含一个分区,只有一个副本: - -``` -`> bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test -``` - -如果我们运行list topic命令,我们现在可以看到该主题: - -``` -`> bin/kafka-topics.sh --list --zookeeper localhost:2181 test -``` - -或者,您也可以将代理配置为在发布不存在的主题时自动创建主题,而不是手动创建主题。 - -查看Topic的信息 - -```shell -./kafka-topics.sh --describe --zookeeper localhost:2181 --topic Hello-Kafka -``` - -运行生产者,然后在控制台中键入一些消息以发送到服务器。 - -``` -> bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test -This is a message -This is another message` -``` - -运行消费者,查看收到的消息 - -```shell -> bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic test --from-beginning -> This is a message -> This is another message -``` - -## Kafka 工程实例 -### POM 依赖 - -```xml - - org.apache.kafka - kafka-clients - 2.1.0 - -``` - -### 生产者 - -编写生产者 Java 代码。关于 Properties 中的值的意思描述可以在官方文档中找到 [http://kafka.apache.org/](http://kafka.apache.org/documentation.html#producerconfigs) 。下面的生产者向 Kafka 推送了10条消息。 - -```java -import org.apache.kafka.clients.producer.KafkaProducer; -import org.apache.kafka.clients.producer.Producer; -import org.apache.kafka.clients.producer.ProducerRecord; -import java.util.Properties; - -/** - *

- * Kafka生产者,发送10个数据 - * - * @Author niujinpeng - * @Date 2018/11/16 15:45 - */ -public class MyProducer { - - public static void main(String[] args) { - Properties props = new Properties(); - props.put("bootstrap.servers", "192.168.110.132:9092"); - props.put("acks", "all"); - props.put("retries", 0); - props.put("batch.size", 16384); - props.put("linger.ms", 1); - props.put("buffer.memory", 33554432); - props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer"); - props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer"); - - Producer producer = new KafkaProducer<>(props); - for (int i = 0; i < 10; i++) { - producer.send(new ProducerRecord("test", Integer.toString(i), Integer.toString(i))); - } - producer.close(); - } - -} -``` -### 消费者 - -编写消费者 Java 代码。 - -```java - -import org.apache.kafka.clients.consumer.ConsumerRecord; -import org.apache.kafka.clients.consumer.ConsumerRecords; -import org.apache.kafka.clients.consumer.KafkaConsumer; - -import java.util.Arrays; -import java.util.Properties; - -/** - *

- * Kafka消费者 - * - * @Author niujinpeng - * @Date 2018/11/19 15:01 - */ -public class MyConsumer { - - public static void main(String[] args) { - - Properties props = new Properties(); - props.put("bootstrap.servers", "192.168.110.132:9092"); - props.put("group.id", "test"); - props.put("enable.auto.commit", "true"); - props.put("auto.commit.interval.ms", "1000"); - props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); - props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer"); - KafkaConsumer consumer = new KafkaConsumer<>(props); - consumer.subscribe(Arrays.asList("test")); - while (true) { - ConsumerRecords records = consumer.poll(100); - for (ConsumerRecord record : records) { - System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); - } - } - } - -} -``` -可以在控制台看到成功运行后的输出,由 offset 可以看到已经消费了10条消息。 -```log - INFO | Kafka version : 2.0.0 - INFO | Kafka commitId : 3402a8361b734732 - INFO | Cluster ID: 0Xrk5M1CSJet0m1ut3zbiw - INFO | [Consumer clientId=consumer-1, groupId=test] Discovered group coordinator 192.168.110.132:9092 (id: 2147483647 rack: null) - INFO | [Consumer clientId=consumer-1, groupId=test] Revoking previously assigned partitions [] - INFO | [Consumer clientId=consumer-1, groupId=test] (Re-)joining group - INFO | [Consumer clientId=consumer-1, groupId=test] Successfully joined group with generation 4 - INFO | [Consumer clientId=consumer-1, groupId=test] Setting newly assigned partitions [test-0] -offset = 38, key = 0, value = 0 -offset = 39, key = 1, value = 1 -offset = 40, key = 2, value = 2 -offset = 41, key = 3, value = 3 -offset = 42, key = 4, value = 4 -offset = 43, key = 5, value = 5 -offset = 44, key = 6, value = 6 -offset = 45, key = 7, value = 7 -offset = 46, key = 8, value = 8 -offset = 47, key = 9, value = 9 -``` - -### 问题 - -如果`java.net.InetAddress.getCanonicalHostName` 取到的是主机名。需要修改 Kafka 的配置文件。 - -```shell -vim server.properties -# x.x.x.x是服务器IP -advertised.listeners=PLAINTEXT://x.x.x.x:9092 -``` - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-01-quick-start.md b/docs/springboot/springboot-01-quick-start.md deleted file mode 100644 index e6504dd..0000000 --- a/docs/springboot/springboot-01-quick-start.md +++ /dev/null @@ -1,317 +0,0 @@ ---- -title: Springboot 系列(一)Spring Boot 入门篇 -toc_number: false -date: 2019-01-01 15:14:17 -url: springboot/springboot01-quick-start -tags: -- Springboot -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。 - -## 前言 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/a60be3362289ed4d901bae342a685f84.png) - -由于 J2EE 的开发变得笨重,繁多的配置,错乱的依赖管理,低下的开发效率,复杂的部署流程,第三方技术的集成难度较大等。同时随着复杂项目的演进,微服务分布式架构思想逐渐进入开发者的视野。 - -## 1. Spring Boot 介绍 -`Spring Boot` 提供了一组工具只需要极少的配置就可以快速的构建并启动基于 Spring 的应用程序。解决了传统 Spring 开发需要配置大量配置文件的痛点,同时 `Spring Boot` 对于第三方库设置了合理的默认值,可以快速的构建起应用程序。当然 `Spring Boot` 也可以轻松的自定义各种配置,无论是在开发的初始阶段还是投入生成的后期阶段。 - - -## 2. Spring Boot 优点 -- 快速的创建可以独立运行的 Spring 项目以及与主流框架的集成。 -- 使用嵌入式的 Servlet 容器,用于不需要打成war包。 -- 使用很多的启动器(Starters)自动依赖与版本控制。 -- 大量的自动化配置,简化了开发,当然,我们也可以修改默认值。 -- 不需要配置 XML 文件,无代码生成,开箱即用。 -- 准生产环境的运行时应用监控。 -- 与云计算的天然集成。 - - -## 3. Spring Boot 前置 -说了那么多的 Spring Boot 的好处,那么使用 Spring Boot 需要哪些前置知识呢?我简单列举了一下。 -- Spring 框架的使用。 -- Maven 构建工具的使用。 -- IDEA 或其他开发工具的使用。 - -## 4. Spring Boot 体验 -现在我们已经了解了 Spring Boot 是什么,下面我们将使用 Spring Boot 开发一个入门案例,来体验 Spring Boot 开发姿势是如何的优雅与迅速。 -Spring Boot 官方已经为我们如何快速启动 Spring Boot 应用程序提供了多种方式。 - -你可以在 Spring 官方网站直接生成项目下载导入IDE进行开发。 - -> https://start.spring.io/ - -也可以直接克隆 GitHub 上的初始项目进行体验。 - -```shell -git clone https://github.com/spring-guides/gs-spring-boot.git -cd gs-spring-boot/initial -``` - -这里我们选择后者,直接克隆进入到 initial 文件夹使用 maven 进行编译启动。 - -```shell - mvn package && java -jar target/gs-spring-boot-0.1.0.jar -``` - -第一次编译需要下载需要的依赖,耗时会比较长,编译完成之后紧接着可以看到 Spring 的启动标志。这时 Spring Boot 的 web程序已经运行在8080端口了。 - -```shell -$ curl -s localhost:8080 -Greetings from Spring Boot! -``` - -## 5. Spring Boot 开发 - -下面手动编写一个 Spring Boot 入门案例,快速的开发一个 web mvc 应用。 -项目结构如下: - -![Spring boot 项目结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/8b8a006da0df292015c9895b337a3ba4.png) - -### 5.1 依赖项 - -```xml - - - org.springframework.boot - spring-boot-starter-parent - 2.1.1.RELEASE - - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - -``` - -`spring-boot-starter-parent` 是Spring Boot 的核心依赖,它里面定义了各种在开发中会用到的第三方 jar 的版本信息,因此我们在引入其他的 Spring Boot 为我们封装的启动器的时候都不在需要指定版本信息。如果我们需要自定义版本信息,可以直接覆盖版本属性值即可。 - -`spring-boot-starter-web` 提供 web 以及 MVC 和 validator 等web开发框架的支持。 - -`spring-boot-starter-test` 提供测试模块的支持。如 Junit,Mockito。 - -需要说明的是,Spring Boot 为我们提供了很多的已经封装好的称为启动器(starter)的依赖项。让我们在使用的时候不需要再进行复杂的配置就可以迅速的进行应用集成。所有的官方启动器依赖可以在[这里](https://github.com/spring-projects/spring-boot/tree/v2.1.1.RELEASE/spring-boot-project/spring-boot-starters)查看。 - -> 所有**官方**发布的启动器都遵循类似的命名模式; `spring-boot-starter-*`,这里`*`是指特定类型的应用程序。此命名结构旨在帮助您寻找启动器。 -> -> 注意:编写自己的启动器的时候不应该使用这种命名方式。 - -### 5.2 启动类 - -```java -@SpringBootApplication -public class HelloApplication { - - public static void main(String[] args) { - SpringApplication.run(HelloApplication.class, args); - } - - @Bean - public CommandLineRunner commandLineRunner(ApplicationContext ctx) { - return args -> { - // 开始检查spring boot 提供的 beans - System.out.println("Let's inspect the beans provided by Spring Boot:"); - String[] beanNames = ctx.getBeanDefinitionNames(); - Arrays.sort(beanNames); - for (String beanName : beanNames) { - System.out.println(beanName); - } - }; - } -} - -``` - -`@SpringBootApplication` 注解是一个便利的注解,它包含了以下几个注解。 - -1. `@Configuration` 定义配置类。 - -2. `@EnableAutoConfiguration` 开启自动配置。 - -3. `@EnableWebMvc` 标记为 web应用程序。 - -4. `@ComponentScan` 组件扫描。 - -### 5.3 控制器 - -```java -@RestController -public class HelloController { - @RequestMapping("/") - public String index() { - return "Greetings from Spring Boot!"; - } -} -``` - -`@RestController` 是 `@Controller` 与 `@ResponseBody` 的结合体。 - -### 5.4 访问测试 - -直接启动 `HelloApplication.java` 类就可以在控制台看到启动输出,然后访问8080端口查看启动是否正常。 - -![Spring boot 项目结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/f8d782a2b291c6e082c116f5792088bb.png) - -经过上面的例子,已经使用 Spring Boot 快速的创建了一个 web 应用并进行了简单的访问测试。 - -## 6. Spring Boot 单元测试 - -结合上面提到的 Spring Boot 启动器知识,Spring Boot 已经为我们提供了丰富的第三方框架,测试框架也不例外。 - -导入单元测试依赖。 - -```java - - org.springframework.boot - spring-boot-starter-test - test - -``` - -### 6.1 模拟请求测试 - -编写单元测试 - -```java -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -/** - * 单元测试 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class HelloApplicationTests { - - @Autowired - private MockMvc mvc; - - @Test - public void contextLoads() throws Exception { - mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string("Greetings from Spring Boot!")); - } - -} - -``` - -关于上面代码的一些说明。 - -- **MockMvc** 允许我们方便的发送 HTTP 请求。 -- **SpringBootTest** 方便的创建一个 Spring Boot 项目的测试程序。 - -运行没有任何异常说明程序测试通过。 - -### 6.2 Spring Boot 集成测试 - -```java - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.web.client.TestRestTemplate; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.http.ResponseEntity; -import org.springframework.test.context.junit4.SpringRunner; - -import java.net.URL; - -/** - *

- * 嵌入式服务器由随机端口启动webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT - * 并且在运行时发现实际端口@LocalServerPort - * - * @Author niujinpeng - * @Date 2018/12/4 15:02 - */ -@RunWith(SpringRunner.class) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -public class HelloApplicationTestBySpringBoot { - - @LocalServerPort - private int port; - - private URL base; - - @Autowired - private TestRestTemplate template; - - @Before - public void setup() throws Exception { - this.base = new URL("http://localhost:" + port + "/"); - } - - @Test - public void getHello() throws Exception { - ResponseEntity response = template.getForEntity(base.toString(), String.class); - assert (response.getBody().equals("Greetings from Spring Boot!")); - } - -} - -``` - -嵌入式服务器由随机端口启动 `webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT` - -并且在运行时使用注解 `@LocalServerPort` 发现实际端口。 - -运行测试类通过输出。 - -```log -2018-12-06 22:28:01.914 INFO 14320 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' -2018-12-06 22:28:01.914 INFO 14320 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' -2018-12-06 22:28:01.937 INFO 14320 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 23 ms -``` - -文章代码已经上传到 GitHub [Spring Boot 入门案例](https://github.com/niumoo/springboot/tree/master/springboot-hello)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-02-config.md b/docs/springboot/springboot-02-config.md deleted file mode 100644 index e578aa6..0000000 --- a/docs/springboot/springboot-02-config.md +++ /dev/null @@ -1,499 +0,0 @@ ---- -title: Springboot 系列(二)Spring Boot 配置文件 -toc_number: false -date: 2019-01-05 22:14:17 -url: springboot/springboot01-config -tags: -- Springboot -- Springboot properties -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。 - -## 前言 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/dc649482e7380cbe546f92550cef3c51.png) - -不管是通过官方提供的方式获取 Spring Boot 项目,还是通过 IDEA 快速的创建 Spring Boot 项目,我们都会发现在 resource 有一个配置文件 `application.properties`,也有可能是`application.yml`.这个文件也就是 Spring Boot 的配置文件。 - - - -## 1. YAML 文件 - -在 `Spring Boot` 中,官方推荐使用 `properties` 或者 `YAML` 文件来完成配置,对于 `YAML` 文件格式还不了解的可以查看官方的具体格式,这里只做简单介绍。 - -**YAML 语法规则:** - -- 大小写敏感 -- 缩进表示层级 -- 缩进只能使用空格 -- 空格的数量不重要,但是相同层级的元素要左侧对齐 -- ` #` 开头的行表示注释 - -**YAML 支持的数据结构:** - -1. 单纯的变量,不可再分的单个的值,如数字,字符串等。 - - ```yaml - name: Darcy - age: 12 - # ~表示NULL值 - email: ~ - # 多行字符串可以使用|保留换行符,也可以使用>折叠换行。 - # +表示保留文字块末尾的换行,-表示删除字符串末尾的换行。 - message:|- - Hello world - ``` - -2. 数组,一组按次序排列的值。 - - ```yaml - lang: - - java - - golang - - c - # 或者行内写法 - lang:[java,golang,c] - ``` - -3. 对象,键值对的集合。 - - ```yaml - person: - name:Darcy - age:20 - # 或者行内写法 - person:{name:Darcy,age:20} - ``` - -使用 `YAML` 支持的三种数据结构通过组合可以形成复杂的复合结构。 - -```YAML -# 服务启动端口号 -server: - port: 8080 -# 配置person属性值 -person: - last-name: Darcy - age: 20 - birth: 2018/01/01 - email: gmail@gmail.com - maps: - key1:java - key2:golang - lists: - - a - - b - - c - dog: - name: 旺财 - age: 2 -``` - -需要注意的是 `YAML` 文件不能使用`@PropertySource` 加载 - -## 2. Properties 文件 - -`properties` 配置文件简单好用,在各种配置环境里都可以看到它的身影,它简单易用,但是在配置复杂结构时不如` YAML` 优雅美观。同样拿上面的 `YAML` 的复合结构举例,演示同样的配置在 `properties `文件中的写法。 - -```properties -# 服务启动端口号 -server.port=8080 -# 配置属性值(使用IDE进行配置需要处理编码问题,不然中文会发送乱码现象) -person.last-name=张三 -person.age=18 -person.birth=2018/12/06 -person.email=niu@gmail.com -person.maps.key1=c -person.maps.key2=java -person.maps.key3=golang -person.lists=a,b,c,d -person.dog.name=旺财 -person.dog.age=1 -``` - -## 3. 随机数与占位符 - -`RandomValuePropertySource` 类对于注入随机值很有用(例如,注入秘密或测试用例)。它可以生成整数,长整数,uuid 或字符串等,通过 Spring Boot 对我们的封装,我们可以轻松的使用。 - -占位符允许在配置的值中引用之前定义过的变量。 - -```properties -# 生成随机值 -bootapp.secret=$ {random.value} -bootapp.number=$ {random.int} -bootapp.bignumber=$ {random.long} -bootapp.uuid=$ {random.uuid} -bootapp.number.less.than.ten=$ {random.int(10)} -bootapp.number.in.range=$ {random.int [1024,65536]} -# 属性的占位符 -bootapp.name=SpringBoot -bootapp.description=${bootapp.name}是一个spring应用程序 -``` - -## 4. 配置的使用 - -通过上面的介绍,可以发现不管是使用 `YAML` 还是 `Properties` 都可以进行配置文件的编写,但是还不知道具体的使用方式,通过下面的几个注解,可以让我们了解到这些配置的具体使用方式。 - -在使用配置之前,添加所需依赖。 - -```xml - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - -``` - - - -### 4.1 ConfigurationProperties - -`@ConfigurationProperties` 注解是 `Spring Boot` 提供的一种使用属性的注入方法。不仅可以方便的把配置文件中的属性值与所注解类绑定,还支持松散绑定,JSR-303 数据校验等功能。以上面演示的 `Properties ` 的配置为例演示 `@ConfigurationProperties` 注解的使用。 - -```java -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.Email; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** - *

- * @Author niujinpeng - * @Date 2018/12/6 22:54 - */ - -@Data -@Component -@ConfigurationProperties(prefix = "person") -@Validated -public class Person { - private String lastName; - private Integer age; - private Date birth; - private Map maps; - private List lists; - private Dog dog; - /** - * 支持数据校验 - */ - @Email - private String email; - -} -``` - -- `@Data ` 是 Lombok 的注解,会为这个类所有属性添加 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法。 -- `@Component` 自动添加 bean 到 spring 容器中。 -- `@ConfigurationProperties` 告诉这个类的属性都是配置文件里的属性,prefix 指定读取配置文件的前缀。 - -### 4.2 Value - -`@Value` 支持直接从配置文件中读取值,同时支持 SpEL 表达式,但是不支持复杂数据类型和数据验证,下面是具体的使用。 - -```java -import lombok.Data; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -import javax.validation.constraints.Email; -import java.util.Date; -import java.util.List; -import java.util.Map; - -@Data -@Component -@Validated -public class PersonValue { - - /** - * 直接从配置文件读取一个值 - */ - @Value("${person.last-name}") - private String lastName; - - /** - * 支持SpEL表达式 - */ - @Value("#{11*4/2}") - private Integer age; - - @Value("${person.birth}") - private Date birth; - - /** - * 不支持复杂类型 - */ - private Map maps; - private List lists; - private Dog dog; - - /** - * 不支持数据校验 - */ - @Email - @Value("xxx@@@@") - private String email; -} - -``` - - - -编写单元测试代码测试代码查看属性绑定是否成功。 - -```java - -import net.codingme.boot.domain.Person; -import net.codingme.boot.domain.PersonValue; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@RunWith(SpringRunner.class) -@SpringBootTest -@AutoConfigureMockMvc -public class HelloApplicationTests { - - @Autowired - private MockMvc mvc; - @Autowired - private Person person; - @Autowired - private PersonValue personValue; - - /** - * 模拟请求测试 - * - * @throws Exception - */ - @Test - public void testGetHello() throws Exception { - mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON)) - .andExpect(status().isOk()) - .andExpect(content().string("Greetings from Spring Boot!")); - } - - /** - * 测试@ConfigurationProperties - */ - @Test - public void testPersion() { - System.out.println(person); - } - - /** - * 测试@Value 引入配置值 - */ - @Test - public void testPersionValue() { - System.out.println(personValue); - } - - -} - -``` - -运行发现数据已经正常绑定。 - -![单元测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/69bca4a567c0c6a4d5d2434812eb65b7.png) - -通过上面的示例,也可以发现 `@ConfigurationProperties` 和 `@Value`的区别。 - -| 特征 | @ConfigurationProperties | @Value | -| ---------------------- | ------------------------ | ------------ | -| 功能 | 批量注入配置文件属性 | 一个一个注入 | -| 松散绑定(松散的语法) | 支持 | 不支持 | -| SpEL | 不支持 | 支持 | -| JSR-303 数据校验 | 支持 | 不支持 | -| 复杂类型 | 支持 | 不支持 | - -`@ConfigurationProperties` 和 `@Value`的使用场景。 - -如果说,只是在某个业务逻辑中获取配置文件的某个值,使用 `@Value`. - -如果说,专门编写有一个 Java Bean 来和配置文件映射,使用 `@ConfigurationProperties`. - -### 4.3 PropertySource - -随着业务复杂性的增加,配置文件也越来越多,我们会觉得所有的配置都写在一个 properties 文件会使配置显得繁杂不利于管理,因此希望可以把映射属性类的配置单独的抽取出来。由于 Spring Boot 默认读取` application.properties`,因此在抽取之后之前单独的`@ConfigurationProperties(prefix = "person")`已经无法读取到信息。这是可以使用 `@PropertySource` 注解来指定要读取的配置文件。 - -需要注意的是,使用 `@PropertySource` 加载自定义的配置文件,,由于 `@PropertySource` 指定的文件会优先加载,所以如果在 `applocation.properties ` 中存在相同的属性配置,会覆盖前者中对于的值。 - -如果抽取 `person` 配置为单独文件`domain-person.properties`。 - -```java -import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.annotation.PropertySource; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; -import javax.validation.constraints.Email; -import java.util.Date; -import java.util.List; -import java.util.Map; - -/** - *

- * @Author niujinpeng - * @Date 2018/12/6 22:54 - */ - -@Data -@Component -@Validated -@PropertySource(value = "classpath:domain-person.properties") -@ConfigurationProperties(value = "person") -public class PersonSource { - - private String lastName; - private Integer age; - private Date birth; - private Map maps; - private List lists; - private Dog dog; - - /** - * 支持数据校验 - */ - @Email - private String email; -} -``` - - -## 5. 多环境配置 - -在主配置文件编写的时候,文件名可以是 `application-{name}.properties`.默认使用的是`application.properties`. - -### 5.1 properties 多环境 - -那么如何在配置文件中激活其他的配置文件呢?只需要在 `application.properties` 启用其他文件。 - -```properties -# 激活 application-prod.properties文件 -spring.profiles.active=prod -``` - -### 5.2 YAML 多环境 - -如果是使用 YAML 配置文件,我们可以使用文件块的形式,在一个 YAML 文件就可以达到多文件配置的效果,下面是 Spring Boot 使用 YAML 文件进行多环境配置的方式。 - -```yaml -server: - port: 8083 - profiles: - active: dev # 指定环境为dev -# 使用三个---进行文档块区分 ---- -server: - port: 8084 -spring: - profiles: dev ---- -server: - port: 8085 -spring: - profiles: prod -``` - -### 5.3 多环境激活方式 - -除了以上的两种配置文件激活方式之外,还有另外两种种激活方式。 - -- 命令行 ,运行时添加 `--spring.profiles.active=prod` -- Jvm 参数 ,运行时添加 `-Dspring.profiles.active=prod` - -如果需要激活其他的配置文件,可以使用 `spring.config.location=G:/application.properties ` 进行配置。 - -## 6. 配置文件加载顺序 - -配置文件默认会从四个地方加载,且优先级从高到低。优先级高的配置会覆盖优先级低的配置。如果多个位置的配置同时存在,不同的配置信息会形成互补配置。 - -```java --file: ./config/ --file: ./ --classpath: /config/ --classpath: / -``` - - - -## 7. 外部配置文件 - -Spring Boot 的外部配置文件加载的方式有很多,具体可以参考[官方文档](https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/boot-features-external-config.html)。这些配置加载优先级从高到底,优先级高的配置会覆盖优先级低的配置。 - -下面介绍几种常见的加载配置的顺序。 - -1. 命令行参数运行,所有的配置都可以在命令行上执行,多个配置空格隔开。 - - ```shell - java -jar springboot-0.0.1-SNAPSHOT.jar --server.port=9999 --sercer.context-path=/spring - ``` -2. jar 包目录下的 application-{profile}.properties (或yml)文件 -3. jar 包里的 application-{profile}.properties (或yml)文件 -4. jar 包目录下的 application.properties (或yml)文件 -5. jar 包里下的 application.properties (或yml)文件 - -文章代码已经上传到 GitHub [Spring Boot 配置文件](https://github.com/niumoo/springboot/tree/master/springboot-properties)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-03-auto-config.md b/docs/springboot/springboot-03-auto-config.md deleted file mode 100644 index ce421b7..0000000 --- a/docs/springboot/springboot-03-auto-config.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -title: Springboot 系列(三)Spring Boot 自动配置 -toc_number: false -date: 2019-01-10 23:01:01 -url: springboot/springboot03-auto-config -tags: -- Springboot -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。 - -## 前言 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/90be321394ddff7aa6dfdc9910888fda.png) - -关于配置文件可以配置的内容,在 [Spring Boot 官方网站](https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#common-application-properties)已经提供了完整了配置示例和解释。 - -可以这么说,Spring Boot 的一大精髓就是自动配置,为开发省去了大量的配置时间,可以更快的融入业务逻辑的开发,那么自动配置是怎么实现的呢? - -## 1. `@SpringBootApplication` - -跟着 Spring Boot 的启动类的注解 `@SpringBootApplication` 进行源码跟踪,寻找自动配置的原理。 - -```java -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@SpringBootConfiguration -@EnableAutoConfiguration -@ComponentScan( - excludeFilters = {@Filter( - type = FilterType.CUSTOM, - classes = {TypeExcludeFilter.class} -), @Filter( - type = FilterType.CUSTOM, - classes = {AutoConfigurationExcludeFilter.class} -)} -) -public @interface SpringBootApplication { -``` - -`@EnableAutoConfiguration` 开启自动配置。 - -`@ComponentScan` 开启注解扫描 - -从 `SpringBootApplication` 我们可以发现,这是一个简便的注解配置,它包含了自动配置,配置类,包扫描等一系列功能。 - -## 2. `@EnableAutoConfiguration` - -继续跟踪,查看`@EnableAutoConfiguration` 源码,里面比较重要的是 `@Import` ,导入了一个翻译名为自动配置的选择器的类。这个类其实就是自动配置的加载选择器。 - -```java -@Target({ElementType.TYPE}) -@Retention(RetentionPolicy.RUNTIME) -@Documented -@Inherited -@AutoConfigurationPackage -@Import({AutoConfigurationImportSelector.class}) -public @interface EnableAutoConfiguration { - String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; - - Class[] exclude() default {}; - - String[] excludeName() default {}; -} - -``` - -继续跟踪 `AutoConfigurationImportSelector.class` .在这个类有一个重要的方法 `getCandidateConfigurations`.用于加载 Spring Boot 配置的自动配置类。 - -`getAutoConfigurationEntry` 会筛选出有效的自动配置类。 - -```java -protected AutoConfigurationEntry getAutoConfigurationEntry( - AutoConfigurationMetadata autoConfigurationMetadata, - AnnotationMetadata annotationMetadata) { - if (!isEnabled(annotationMetadata)) { - return EMPTY_ENTRY; - } - AnnotationAttributes attributes = getAttributes(annotationMetadata); - List configurations = getCandidateConfigurations(annotationMetadata, - attributes); - configurations = removeDuplicates(configurations); - Set exclusions = getExclusions(annotationMetadata, attributes); - checkExcludedClasses(configurations, exclusions); - configurations.removeAll(exclusions); - configurations = filter(configurations, autoConfigurationMetadata); - fireAutoConfigurationImportEvents(configurations, exclusions); - return new AutoConfigurationEntry(configurations, exclusions); - } - -protected List getCandidateConfigurations(AnnotationMetadata metadata, - AnnotationAttributes attributes) { - List configurations = SpringFactoriesLoader.loadFactoryNames( - getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader()); - Assert.notEmpty(configurations, - "No auto configuration classes found in META-INF/spring.factories. If you " - + "are using a custom packaging, make sure that file is correct."); - return configurations; - } -``` -下图是 DEBUG 模式下筛选之后的结果,因为我只添加了 web 模块,所以只有 web 相关的自动配置。 - -![筛选过后的自动配置](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/50733348-ec781400-11c6-11e9-8f0d-01797d797d69.png) - -## 3. xxxAutoConfiguration 与 xxxProperties - -在上面的 debug 里,我们看到了成功加载的自动配置,目前只看到了配置类,却还没有发现自动配置值,随便选择一个 `AutoConfiguration` 查看源码。 - -这里选择了 `ServletWebServerFactoryAutoConfiguration`. - -```java -@Configuration -@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) -//判断当前项目有没有这个类 -//CharacterEncodingFilter;SpringMVC中进行乱码解决的过滤器; -@ConditionalOnClass(ServletRequest.class) -//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果 -//满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效 -@ConditionalOnWebApplication(type = Type.SERVLET) -@EnableConfigurationProperties(ServerProperties.class) -@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, - ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, - ServletWebServerFactoryConfiguration.EmbeddedJetty.class, - ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) -public class ServletWebServerFactoryAutoConfiguration { -``` - -需要注意的是 `@EnableConfigurationProperties(ServerProperties.class)`.他的意思是启动指定类的 -`ConfigurationProperties`功能;将配置文件中对应的值和 `ServerProperties` 绑定起来;并把 -`ServerProperties` 加入到 IOC 容器中。 - -再来看一下 `ServerProperties` . - -```java -@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) -public class ServerProperties { - - /** - * Server HTTP port. - */ - private Integer port; -``` - -显而易见了,这里使用 ConfigurationProperties 绑定属性映射文件中的 server 开头的属性。结合默认配置 -``` -# 路径spring-boot-autoconfigure-2.1.1.RELEASE.jar -# /META-INF/spring-configuration-metadata.json - - { - "name": "server.port", - "type": "java.lang.Integer", - "description": "Server HTTP port.", - "sourceType": "org.springframework.boot.autoconfigure.web.ServerProperties", - "defaultValue": 8080 - } -``` -达到了自动配置的目的。 - -## 4. 自动配置总结 - -1. SpringBoot 启动的时候加载主配置类,开启了自动配置功能 @EnableAutoConfiguration 。 -2. @EnableAutoConfiguration 给容器导入META-INF/spring.factories 里定义的自动配置类。 -3. 筛选有效的自动配置类。 -4. 每一个自动配置类结合对应的 xxxProperties.java 读取配置文件进行自动配置功能 。 - -## 5. 配置类 - -通过自动配置,我们发现已经帮我们省去了大量的配置文件的编写,那么在自定义配置的时候,我们是不是需要编写XML呢?Spring boot 尽管可以使用 `SpringApplication`XML 文件进行配置,但是我们通常会使用 `@Configuration` 类进行代替,这也是官方推荐的方式。 - -### 5.1 XML配置 - -定义 helloService Bean. - -```xml - - - - - - -``` - -引入配置。 - -```java -@ImportResource(value = "classpath:spring-service.xml") -@SpringBootApplication -public class BootApplication { - - public static void main(String[] args) { - SpringApplication.run(BootApplication.class, args); - } -} -``` - -### 5.2 注解配置 - -此种方式和上面的XML配置是等效的,也是官方推荐的方式。`@Configuration` 注解的类(要在扫描的包路径中)会被扫描到。 - -```java -/** - *

- * 配置类,相当于传统Spring 开发中的 xml-> bean的配置 - * - * @Author niujinpeng - * @Date 2018/12/7 0:04 - */ -@Configuration -public class ServiceConfig { - - /** - * 默认添加到容器中的 ID 为方法名(helloService) - * - * @return - */ - @Bean - public HelloService helloService() { - return new HelloService(); - } -} -``` - - - -## 6. 附录 - -| @Conditional扩展注解 | 作用(判断是否满足当前指定条件) | -| ------------------------------- | ------------------------------------------------ | -| @ConditionalOnJava | 系统的java版本是否符合要求 | -| @ConditionalOnBean | 容器中存在指定Bean; | -| @ConditionalOnMissingBean | 容器中不存在指定Bean; | -| @ConditionalOnExpression | 满足SpEL表达式指定 | -| @ConditionalOnClass | 系统中有指定的类 | -| @ConditionalOnMissingClass | 系统中没有指定的类 | -| @ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean | -| @ConditionalOnProperty | 系统中指定的属性是否有指定的值 | -| @ConditionalOnResource | 类路径下是否存在指定资源文件 | -| @ConditionalOnWebApplication | 当前是web环境 | -| @ConditionalOnNotWebApplication | 当前不是web环境 | -| @ConditionalOnJndi | JNDI存在指定项 | - - - -文章代码已经上传到 GitHub [Spring Boot 自动配置](https://github.com/niumoo/springboot/tree/master/springboot-config)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-04-log.md b/docs/springboot/springboot-04-log.md deleted file mode 100644 index 750ce15..0000000 --- a/docs/springboot/springboot-04-log.md +++ /dev/null @@ -1,225 +0,0 @@ ---- -title: Springboot 系列(四)Spring Boot 日志框架 -toc_number: false -date: 2019-01-15 22:02:02 -url: springboot/springboot04-log -tags: - - Springboot - - Springboot2 - - Logback -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。 - -## 前言 -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6ceaab90e32e1c0de8dd4ed097900509.png)Spring 框架选择使用了 JCL 作为默认日志输出。而 Spring Boot 默认选择了 SLF4J 结合 LogBack。那我们在项目中该使用哪种日志框架呢?在对于不同的第三方 jar 使用了不同的日志框架的时候,我们该怎么处理呢? - - -## 1. 日志框架介绍 - -日志对于应用程序的重要性不言而喻,不管是记录运行情况还是追踪线上问题,都离不开对日志的分析,在 Java 领域里存在着多种日志框架,如 JUL, Log4j, Log4j2, Commons Loggin, Slf4j, Logback 等。关于 Log4j, Log4j2 和 Slf4j 直接的故事这里不做介绍,有兴趣可以自行百度。 - -## 2. SLF4 的使用 - -在开发的时候不应该直接使用日志实现类,应该使用日志的抽象层。具体参考 [SLF4J 官方](https://www.slf4j.org/manual.html)。 -下图是 SLF4J 结合各种日志框架的官方示例,从图中可以清晰的看出 SLF4J API 永远作为日志的门面,直接应用与应用程序中。 -![SLF4](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/180c77a1bfd179623888aa83faf4519d.png) - -同时 SLF4 官方给出了简单示例。 - -```java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HelloWorld { - public static void main(String[] args) { - Logger logger = LoggerFactory.getLogger(HelloWorld.class); - logger.info("Hello World"); - } -} -``` -需要注意的是,要为系统导入 SLF4J 的 jar 和 日志框架的实现 jar. 由于每一个日志的实现框架都有自己的配置文件,所以在使用 SLF4 之后,配置文件还是要使用实现日志框架的配置文件。 - - -## 3. 统一日志框架的使用 - -一般情况下,在项目中存在着各种不同的第三方 jar ,且它们的日志选择也可能不尽相同,显然这样是不利于我们使用的,那么如果我们想为项目设置统一的日志框架该怎么办呢? - -在 [SLF4J 官方](https://www.slf4j.org/legacy.html),也给了我们参考的例子。 - -![Bridging legacy APIs](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4d3f93aedfdfff372bf2908f5ef876bd.png)从图中我们得到一种统一日志框架使用的方式,可以使用一种和要替换的日志框架类完全一样的 jar 进行替换,这样不至于原来的第三方 jar 报错,而这个替换的 jar 其实使用了 SLF4J API. 这样项目中的日志就都可以通过 SLF4J API 结合自己选择的框架进行日志输出。 -**统一日志框架使用步骤归纳如下**: - -1. 排除系统中的其他日志框架。 -2. 使用中间包替换要替换的日志框架。 -3. 导入我们选择的 SLF4J 实现。 - - -## 4. Spring Boot 的日志关系 - -### 4.1. 排除其他日志框架 - -根据上面总结的要统一日志框架的使用,第一步要排除其他的日志框架,在 Spring Boot 的 Maven 依赖里可以清楚的看到 Spring Boot 排除了其他日志框架。 -![Spring Boot 排除其他日志框架](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/f24ff63ebb75a90c0d85b3dd74840cb5.png)我们自行排除依赖时也只需要按照图中的方式就好了。 - - -### 4.2. 统一框架引入替换包 - -其实 Spring Boot 也是使用了 SLF4J+logback 的日志框架组合,查看 Spring Boot 项目的 Maven 依赖关系可以看到 Spring Boot 的核心启动器 spring-boot-starter 引入了 spring-boot-starter-logging. -```xml - - org.springframework.boot - spring-boot-starter-logging - 2.1.1.RELEASE - compile - -``` -而 spring-boot-starter-logging 的 Maven 依赖主要引入了 logback-classic (包含了日志框架 Logback 的实现),log4j-to-slf4j (在 log4j 日志框架作者开发此框架的时候还没有想到使用日志抽象层进行开发,因此出现了 log4j 向 slf4j 转换的工具),jul-to-slf4j ( Java 自带的日志框架转换为 slf4j). -```xml - - - ch.qos.logback - logback-classic - 1.2.3 - compile - - - org.apache.logging.log4j - log4j-to-slf4j - 2.11.1 - compile - - - org.slf4j - jul-to-slf4j - 1.7.25 - compile - - -``` -从上面的分析,Spring Boot 对日志框架的使用已经是清晰明了了,我们使用 IDEA 工具查看 Maven 依赖关系,可以清晰的看到日志框架的引用。如果没有 IDEA 工具,也可以使用 Maven 命令查看依赖关系。 -```shell -mvn dependency:tree -``` -![Spring Boot Maven 依赖](https://user-images.githubusercontent.com/26371673/50733360-33660980-11c7-11e9-8742-1f24e7449db2.png)由此可见,Spring Boot 可以自动的适配日志框架,而且底层使用 **SLF4 + LogBack** 记录日志,如果我们自行引入其他框架,需要排除其日志框架。 - -## 5. Spring Boot 的日志使用 - -### 5.1. 日志级别和格式 -从上面的分析,发现 Spring Boot 默认已经使用了 **SLF4J + LogBack** . 所以我们在不进行任何额外操作的情况下就可以使用 **SLF4J + Logback** 进行日志输出。 -编写 Java 测试类进行测试。 -```java -import org.junit.Test; -import org.junit.runner.RunWith; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -/** - *

- * 测试日志输出, - * SLF4J 日志级别从小到大trace,debug,info,warn,error - * - * @Author niujinpeng - * @Date 2018/12/11 21:12 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class LogbackTest { - - Logger logger = LoggerFactory.getLogger(getClass()); - - @Test - public void testLog() { - logger.trace("Trace 日志..."); - logger.debug("Debug 日志..."); - logger.info("Info 日志..."); - logger.warn("Warn 日志..."); - logger.error("Error 日志..."); - } -} - -``` -已知日志级别从小到大为 trace < debug < info < warn < error . 运行得到输出如下。由此可见 ***Spring Boot 默认日志级别为 INFO***. -```log -2018-12-11 23:02:58.028 [main] INFO n.c.boot.LogbackTest - Info 日志... -2018-12-11 23:02:58.029 [main] WARN n.c.boot.LogbackTest - Warn 日志... -2018-12-11 23:02:58.029 [main] ERROR n.c.boot.LogbackTest - Error 日志... -``` -从上面的日志结合 Logback 日志格式可以知道 Spring Boot 默认日志格式是。 -```shell -%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n -# %d{yyyy-MM-dd HH:mm:ss.SSS} 时间 -# %thread 线程名称 -# %-5level 日志级别从左显示5个字符宽度 -# %logger{50} 类名 -# %msg%n 日志信息加换行 -``` -至于为什么 Spring Boot 的默认日志输出格式是这样? -![Spring Boot 默认日志输出](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/09754e106f158d8e2dfcf540ca00cb2c.png)我们可以在 Spring Boot 的源码里找到答案。 - -### 5.2 自定义日志输出 -可以直接在配置文件编写日志相关配置。 -```yaml -# 日志配置 -# 指定具体包的日志级别 -logging.level.net.codingme=debug -# 控制台和日志文件输出格式 -logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n -logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n -# 日志文件大小 -logging.file.max-size=10MB -# 保留的日志时间 -logging.file.max-history=10 -# 日志输出路径,默认文件spring.log -logging.path=systemlog -#logging.file=log.log -``` - -关于日志的输出路径,可以使用 logging.file 或者 logging.path 进行定义,两者存在关系如下表。 - -| `logging.file` | `logging.path` | 例子 | 描述 | -| -------------- | -------------- | ---------- | ------------------------------------------------------------ | -| *(没有)* | *(没有)* | | 仅控制台记录。 | -| 具体文件 | *(没有)* | `my.log` | 写入指定的日志文件,名称可以是精确位置或相对于当前目录。 | -| *(没有)* | 具体目录 | `/var/log` | 写入`spring.log`指定的目录,名称可以是精确位置或相对于当前目录。 | - - -## 6. 替换日志框架 -因为 Log4j 日志框架已经年久失修,原作者都觉得写的不好,所以下面演示替换日志框架为 Log4j2 的方式。根据[官网](https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/using-boot-build-systems.html#using-boot-starter)我们 Log4j2 与 logging 需要二选一,因此修改 pom如下。 - -```xml - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-logging - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-log4j2 - -``` - -文章代码已经上传到 GitHub [Spring Boot 日志系统](https://github.com/niumoo/springboot/tree/master/springboot-logback)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-05-web-static-template.md b/docs/springboot/springboot-05-web-static-template.md deleted file mode 100644 index 2d7d442..0000000 --- a/docs/springboot/springboot-05-web-static-template.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -title: Springboot 系列(五)web 开发之静态资源和模版引擎 -toc_number: false -date: 2019-02-15 22:32:01 -url: springboot/springboot-05-web-static-template -tags: - - Springboot - - Thymelaf - - FreeMarker -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 前言 -Spring Boot 天生的适合 web 应用开发,它可以快速的嵌入 Tomcat, Jetty 或 Netty 用于包含一个 HTTP 服务器。且开发十分简单,只需要引入 web 开发所需的包,然后编写业务代码即可。 - -## **自动配置原理?** - -在进行 web 开发之前让我再来回顾一下自动配置,可以参考系列文章第三篇。Spring Boot 为 Spring MVC 提供了自动配置,添加了如下的功能: - -- 视图解析的支持。 -- 静态资源映射,WebJars 的支持。 -- 转换器 Converter 的支持。 -- 自定义 Favicon 的支持。 -- 等等 - -在引入每个包时候我们需要思考是如何实现自动配置的,以及我们能自己来配置哪些东西,这样开发起来才会得心应手。 - -[关于 Spring Boot Web 开发的更详细介绍可以参考官方文档。](https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/boot-features-developing-web-applications.html) - -## 1. JSON 格式转换 - -Spring Boot 默认使用 Jackson 进行 JSON 化处理,如果想要切换成 FastJson 可以首先从[官方文档](https://docs.spring.io/spring-boot/docs/2.1.x/reference/html/howto-spring-mvc.html#howto-customize-the-responsebody-rendering)里查询信息。从这里知道对于 ResponseBody 的渲染主要是通过 HttpMessageConverters, 而首先引入FastJson Pom依赖并排除 Spring Boot 自带的 Jackson。 - -```xml - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - com.alibaba - fastjson - 1.2.47 - -``` - -编写转换器处理 json 的日期格式同时处理中文乱码问题。 - -```java -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - /** - * 自定义JSON转换器 - * - * @param converters - */ - @Override - public void configureMessageConverters(List> converters) { - FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); - FastJsonConfig fastJsonConfig = new FastJsonConfig(); - fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); - //日期格式化 - fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); - //处理中文乱码问题 - List fastMediaTypes = new ArrayList<>(); - fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); - - converter.setSupportedMediaTypes(fastMediaTypes); - converter.setFastJsonConfig(fastJsonConfig); - - converters.add(converter); - } - -} -``` - -## 2. 静态资源映射 - -> By default, Spring Boot serves static content from a directory called `/static` (or `/public` or `/resources` or `/META-INF/resources`) in the classpath or from the root of the `ServletContext`. -### 2.1 默认映射 -官方文档告诉我们 Spring Boot 对于静态资源的映射目录是 /static , /public , /resources 以及 /META-INF/resource。除此之外其实还映射了 `/webjars/**` 到 `classpath:/META-INF/resources/webjars`。 - -很明显此处是自动配置实现的,通过查看源码分析这段配置。 - -![Mvc静态资源映射](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/746f53d54281496bb8aafcc2e7f1ada6.png) - -![Mvc静态资源映射](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/03afb17acb7e7a3ccd45a5b434f111d1.png) - -而对于网站图标,Spring Boot 也已经配置了默认位置,可以在看到。 - -```java -// path: org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration -@Bean -public SimpleUrlHandlerMapping faviconHandlerMapping() { - SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); - mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); - mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", // 图表 - faviconRequestHandler())); - return mapping; -} - -@Bean -public ResourceHttpRequestHandler faviconRequestHandler() { - ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); - requestHandler.setLocations(resolveFaviconLocations()); - return requestHandler; -} - -private List resolveFaviconLocations() { - String[] staticLocations = getResourceLocations( - this.resourceProperties.getStaticLocations()); - List locations = new ArrayList<>(staticLocations.length + 1); - Arrays.stream(staticLocations).map(this.resourceLoader::getResource) - .forEach(locations::add); - locations.add(new ClassPathResource("/")); - return Collections.unmodifiableList(locations); -} -``` - -根据 Spring Boot 默认的静态资源映射规则,可以直接把需要的静态资源放在响应的文件夹下然后直接引用即可。 - -![静态资源映射](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/4d1f6c2368dce288a2187741fc4dc0c7.png) - -而放在 Public 文件夹下的 HTML 页面也可以直接访问。 -![静态资源映射](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/79377b2dadbeee45f00da8921190938b.png) - -### 2.2 webjars - -[webjars](https://www.webjars.org/) 的思想是把静态资源打包到 Jar 包中,然后使用 JVM 构建工具进行管理,如 maven , Gradle 等。 - -使用 webjars 第一步需要进入依赖,如要使用 bootstrap。 - -```xml - - - org.webjars - bootstrap - 4.1.3 - -``` - -引入之后查看 bootstrap 资源。 - -![WebJars 引入 bootstrap](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/3626437cd5104cea9ed5a82ea25aac0c.png) - -由于 Springboot 映射了 `/webjars/**` 到 `classpath:/META-INF/resources/webjars`. 因此可以直接在文件中引用 webjars 的静态资源。 - -```html - - - -``` - - - -## 3. 模版引擎 - -Spring MVC 支持各种模版技术,如 Thymeleaf , FreeMarker , JSP 等。而Thyemeleaf 原型即页面的特性或许更符合 Spring Boot 快速开发的思想而被官方推荐。 - -![模版引擎原理](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/02c76ef91580600fb7265155822d5619.png) - -[Thymeleaf](https://www.thymeleaf.org/) 是适用于 Web 开发的服务端 Java 模版引擎,Thymeleaf 为开发工作流程带来优雅自然的模版,由于其非侵入的特性,可以让页面不管是在静态原型下还是用作模版引擎时都有良好的页面展现。 - -```html - - - - - - - - - - - - - -
NamePrice
Oranges0.99
-``` - -### 3.1 引入 Thymeleaf - -```xml - - - org.springframework.boot - spring-boot-starter-thymeleaf - -``` - -### 3.2 使用 Thymeleaf - -根据 Spring Boot 自动配置原理,先看一下 Thymeleaf 的配置类,从中可以看出 Thymeleaf 的相关配置。我们可以知道 默认存放目录是 templates 文件夹,文件后缀为 `.html` 且开启了缓存。 - -```java -@ConfigurationProperties(prefix = "spring.thymeleaf") -public class ThymeleafProperties { - - private static final Charset DEFAULT_ENCODING = StandardCharsets.UTF_8; - - public static final String DEFAULT_PREFIX = "classpath:/templates/"; - - public static final String DEFAULT_SUFFIX = ".html"; - /** - * Whether to enable template caching. - */ - private boolean cache = true; -``` - -为了在开发中编写模版文件时不用重启,可以在配置中关闭缓存。 - -```properties -# 关闭模版缓存 -spring.thymeleaf.cache=false -# 如果需要进行其他的配置,可以参考配置类:ThymeleafProperties -# org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties -``` - -编写 Controller 响应信息。 - -```java - /** - * 获取ID为1的用户信息 - * - * @return - */ - @GetMapping(value = "/user/1") - public String getUserById(Model model) { - User user1 = new User("Darcy", "password", 24, new Date(), Arrays.asList("Java", "GoLang")); - User user2 = new User("Chris", "password", 22, new Date(), Arrays.asList("Java", "Web")); - ArrayList userList = new ArrayList<>(); - userList.add(user1); - userList.add(user2); - model.addAttribute("userList", userList); - model.addAttribute("user", user1); - return "user"; - } -``` - -因为 Thymelaf 默认模版位置在 templates 文件夹下,因此在这个文件夹下编写页面信息。 - -```html - - - - Thymeleaf 的基本使用 - - - - - -

-

Hello Thymeleaf Index

- 用户名称: -
- 用户技能: -
- 用户年龄: -
- 用户生日: -
- - -
-

Hello Thymeleaf Index

- - 用户名称: -
- 用户技能: -
- 用户年龄: -
- 用户生日: -
- -
-

Text 与 utext

- - abc -
- abc -
- -
-

URL 的引用

- 网站网址 -
- -
-

表单的使用

-
- 用户名称: -
- 用户技能: -
- 用户年龄: - -
-
- -
-

判断的使用

-
18岁了
-
大于18岁
-
小于18岁
-
大于等于
-
小于等于
-
- -
-

选择框

- -
- -
-

遍历功能

- - - - - - - - - - - -
用户名称年龄技能
-
- -
-

Switch功能

-
-

欢迎管理员

-
-
- - -``` -访问页面可以看到数据正常显示。 - -![访问页面](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/5e7b0da4afded072ed59c42a34b3f7fc.png) - -文章代码已经上传到 GitHub [Spring Boot Web开发 - 静态资源](https://github.com/niumoo/springboot/tree/master/springboot-web-staticfile)。 -文章代码已经上传到 GitHub [Spring Boot Web开发 - 模版引擎](https://github.com/niumoo/springboot/tree/master/springboot-web-template)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-06-web-filter-apo-webbase.md b/docs/springboot/springboot-06-web-filter-apo-webbase.md deleted file mode 100644 index 50beca9..0000000 --- a/docs/springboot/springboot-06-web-filter-apo-webbase.md +++ /dev/null @@ -1,536 +0,0 @@ ---- -title: Springboot 系列(六)web 开发之拦截器和三大组件 -toc_number: false -date: 2019-02-21 08:32:01 -url: springboot/springboot-06-web-filter-apo-webbase -tags: - - Springboot -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 1. 拦截器 -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/e678416c36eaf0969de279423d0e1d16.png)Springboot 中的 Interceptor 拦截器也就是 mvc 中的拦截器,只是省去了 xml 配置部分。并没有本质的不同,都是通过实现 HandlerInterceptor 中几个方法实现。几个方法的作用一一如下。 -1. **preHandle** - 进入 Habdler 方法之前执行,一般用于身份认证授权等。 -2. **postHandle** - 进入 Handler 方法之后返回 modelAndView 之前执行,一般用于塞入公共模型数据等。 -3. **afterCompletion** - 最后处理,一般用于日志收集,统一后续处理等。 - - -### 1.1 引入依赖 - -```xml - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.alibaba - fastjson - 1.2.47 - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.junit.jupiter - junit-jupiter-api - RELEASE - compile - - -``` - -### 1.2 编写拦截器 - -```java -package net.codingme.boot.config; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.web.servlet.HandlerInterceptor; -import org.springframework.web.servlet.ModelAndView; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -/** - *

- * 拦截器 - * - * @Author niujinpeng - * @Date 2019/1/6 16:54 - */ -@Slf4j -public class LogHandlerInterceptor implements HandlerInterceptor { - - /** - * 请求方法执行之前 - * 返回true则通过 - * - * @param request - * @param response - * @param handler - * @return - */ - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - StringBuffer requestURL = request.getRequestURL(); - log.info("preHandle请求URL:" + requestURL.toString()); - return true; - } - - /** - * 返回modelAndView之前执行 - * @param request - * @param response - * @param handler - * @param modelAndView - */ - @Override - public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) { - log.info("postHandle返回modelAndView之前"); - } - - /** - * 执行Handler完成执行此方法 - * @param request - * @param response - * @param handler - * @param ex - */ - @Override - public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { - log.info("afterCompletion执行完请求方法完全返回之后"); - } -} -``` - -### 1.3 配置拦截器 - -省去了 XML 中的拦截器配置部分后,使用 springboot 推荐的方式配置自定义拦截器。 - -```java -package net.codingme.boot.config; - - -import com.alibaba.fastjson.serializer.SerializerFeature; -import com.alibaba.fastjson.support.config.FastJsonConfig; -import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; -import org.springframework.context.annotation.Configuration; -import org.springframework.http.MediaType; -import org.springframework.http.converter.HttpMessageConverter; -import org.springframework.web.servlet.config.annotation.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -import java.util.ArrayList; -import java.util.List; - -/** - *

- * 1.使用FastJSON - * 2.配置时间格式化 - * 3.解决中文乱码 - * 4.添加自定义拦截器 - * - * @Author niujinpeng - * @Date 2018/12/13 15:35 - */ -@Configuration -public class WebMvcConfig implements WebMvcConfigurer { - - /** - * 自定义JSON转换器 - * - * @param converters - */ - @Override - public void configureMessageConverters(List> converters) { - FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter(); - FastJsonConfig fastJsonConfig = new FastJsonConfig(); - fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat); - //日期格式化 - fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); - //处理中文乱码问题 - List fastMediaTypes = new ArrayList<>(); - fastMediaTypes.add(MediaType.APPLICATION_JSON_UTF8); - - converter.setSupportedMediaTypes(fastMediaTypes); - converter.setFastJsonConfig(fastJsonConfig); - converters.add(converter); - } - - /** - * 添加自定义拦截器 - * .addPathPatterns("/**") 拦截的请求路径 - * .excludePathPatterns("/user"); 排除的请求路径 - * - * @param registry - */ - @Override - public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new LogHandlerInterceptor()) - .addPathPatterns("/**") - .excludePathPatterns("/user"); - } -} -``` - -## 2 切面编程 - -1. AOP:面向切面(方面)编程,扩展功能不修改源代码实现 -2. AOP采取横向抽取机制,取代了传统纵向继承体系重复性代码 -3. AOP底层使用动态代理实现 - - 有接口情况使用动态代理创建接口实现类代理对象 - - 没有接口情况使用动态代理创建类的子类代理对象 - -```java -import lombok.extern.slf4j.Slf4j; -import org.aspectj.lang.JoinPoint; -import org.aspectj.lang.annotation.*; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.RequestContextHolder; -import org.springframework.web.context.request.ServletRequestAttributes; - -import javax.servlet.http.HttpServletRequest; -import java.util.Arrays; - -/** - *

- * 使用AOP记录访问日志 - * 使用@Before在切入点开始处切入内容 - * 使用@After在切入点结尾处切入内容 - * 使用@AfterReturning在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) - * 使用@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容 - * 使用@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑 - *

- * 注解: - * Aspect:AOP - * Component:Bean - * Slf4j:可以直接使用log输出日志 - * Order:多个AOP切同一个方法时的优先级,越小优先级越高越大。 - * 在切入点前的操作,按order的值由小到大执行 - * 在切入点后的操作,按order的值由大到小执行 - * - * @Author niujinpeng - * @Date 2019/1/4 23:29 - */ - -@Aspect -@Component -@Slf4j -@Order(1) -public class LogAspect { - /** - * 线程存放信息 - */ - ThreadLocal startTime = new ThreadLocal<>(); - - /** - * 定义切入点 - * 第一个*:标识所有返回类型 - * 字母路径:包路径 - * 两个点..:当前包以及子包 - * 第二个*:所有的类 - * 第三个*:所有的方法 - * 最后的两个点:所有类型的参数 - */ - @Pointcut("execution(public * net.codingme.boot.controller..*.*(..))") - public void webLog() { - } - - /** - * 在切入点开始处切入内容 - * - * @param joinPoint - */ - @Before("webLog()") - public void doBefore(JoinPoint joinPoint) { - // 记录请求时间 - startTime.set(System.currentTimeMillis()); - // 获取请求域 - ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - HttpServletRequest request = requestAttributes.getRequest(); - - // 记录请求内容 - log.info("Aspect-URL: " + request.getRequestURI().toLowerCase()); - log.info("Aspect-HTTP_METHOD: " + request.getMethod()); - log.info("Aspect-IP: " + request.getRemoteAddr()); - log.info("Aspect-REQUEST_METHOD: " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName()); - log.info("Aspect-Args: " + Arrays.toString(joinPoint.getArgs())); - } - - /** - * 在切入点之后处理内容 - */ - @After("webLog()") - public void doAfter() { - - } - - /** - * 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理) - */ - @AfterReturning(returning = "ret", pointcut = "webLog()") - public void doAfterReturning(Object ret) throws Throwable { - log.info("Aspect-Response: " + ret); - Long endTime = System.currentTimeMillis(); - log.info("Aspect-SpeedTime: " + (endTime - startTime.get()) + "ms"); - } - -} - -``` - -访问查看拦截器和 AOP 的日志输出。 - -```log -09:57:15.408 INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor : preHandle请求URL:http://localhost:8080/ -09:57:15.413 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-URL: / -09:57:15.413 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-HTTP_METHOD: GET -09:57:15.413 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-IP: 0:0:0:0:0:0:0:1 -09:57:15.414 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-REQUEST_METHOD: net.codingme.boot.controller.HelloController.index -09:57:15.415 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-Args: [] -09:57:15.424 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-Response: Greetings from Spring Boot!SpringBoot是一个spring应用程序 -09:57:15.425 INFO 2836 --- [nio-8080-exec-1] net.codingme.boot.config.LogAspect : Aspect-SpeedTime: 12ms -09:57:15.436 INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor : postHandle返回modelAndView之前 -09:57:15.437 INFO 2836 --- [nio-8080-exec-1] n.c.boot.config.LogHandlerInterceptor : afterCompletion执行完请求方法完全返回之后 -``` - -## 3. Servlet,Filter,Listener -Servlet, Filter, Listener 是 Java web 的核心内容,那么在 Springboot 中如何使用呢? -### 3.1 编写 Servlet - -```java -package net.codingme.boot.servlet; - -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.ServletException; -import javax.servlet.annotation.WebServlet; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; - -/** - *

- * @WebServlet(urlPatterns = "/myservlet") // 定义访问路径 - * @Author niujinpeng - * @Date 2019/1/24 16:25 - */ -@Slf4j -@WebServlet(urlPatterns = "/myservlet") -public class MyServlet extends HttpServlet { - - @Override - public void init() throws ServletException { - log.info("Servlet 开始初始化"); - super.init(); - } - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - log.info("Servlet 开始处理 GET 方法"); - PrintWriter writer = resp.getWriter(); - writer.println("Hello Servlet"); - writer.flush(); - writer.close(); - } - - @Override - protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - doGet(req, resp); - } - - @Override - public void destroy() { - log.info("Servlet 开始销毁"); - super.destroy(); - } -} -``` - -### 3.2 编写 Filter - -```java -package net.codingme.boot.filter; - -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.*; -import javax.servlet.annotation.WebFilter; -import java.io.IOException; - -/** - *

- * - * @Author niujinpeng - * @Date 2019/1/24 16:35 - */ -@Slf4j -@WebFilter(urlPatterns = "/*") -public class MyFilter implements Filter { - - - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { - log.info("拦截器开始拦截"); - filterChain.doFilter(request, response); - } - -} - -``` - -### 3.3 编写 Listener - -```java -package net.codingme.boot.listener; - -import lombok.extern.slf4j.Slf4j; - -import javax.servlet.ServletContextEvent; -import javax.servlet.ServletContextListener; -import javax.servlet.annotation.WebListener; - -/** - *

- * - * @Author niujinpeng - * @Date 2019/1/24 16:45 - */ -@Slf4j -@WebListener -public class MyListener implements ServletContextListener { - - @Override - public void contextInitialized(ServletContextEvent sce) { - log.info("监听器开始初始化"); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - log.info("监听器开始销毁"); - } -} - -``` - -### 3.4 添加到容器 - -添加到容器有两种方式,第一种使用注解扫描。 - -```java -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.web.servlet.ServletComponentScan; -import org.springframework.context.annotation.ComponentScan; - -/** - * @ServletComponentScan 扫描Servlet,Filter,Listener 添加到容器 - */ -@SpringBootApplication -@ServletComponentScan -public class BootApplication { - - public static void main(String[] args) { - SpringApplication.run(BootApplication.class, args); - } - -} -``` - -或者使用配置类想容器中添加。 - -```java -/** - *

- * 在这里注册Servlet Filter Listener 或者使用 @ServletComponentScan - * - * @Author niujinpeng - * @Date 2019/1/24 16:30 - */ -@Configuration -public class WebCoreConfig { - - @Bean - public ServletRegistrationBean myServlet() { - return new ServletRegistrationBean<>(new MyServlet()); - } - - @Bean - public FilterRegistrationBean myFitler() { - return new FilterRegistrationBean<>(new MyFilter()); - } - - @Bean - public ServletListenerRegistrationBean myListener() { - return new ServletListenerRegistrationBean(new MyListener()); - } - -} -``` - -启动可以在控制台看到监听器启动。 - -```log - 11:35:03.744 INFO 8616 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1364 ms - 11:35:03.798 INFO 8616 --- [ main] net.codingme.boot.listener.MyListener : 监听器开始初始化 - 11:35:03.892 INFO 8616 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' - 11:35:04.055 INFO 8616 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' -``` - -访问 Servlet 可以看到拦截器和 Servlet 生效。 - -```log - 11:36:55.552 INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.servlet.MyServlet : Servlet 开始初始化 - 11:36:55.556 INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.filter.MyFilter : 拦截器开始拦截 - 11:36:55.556 INFO 3760 --- [nio-8080-exec-1] net.codingme.boot.servlet.MyServlet : Servlet 开始处理 GET 方法 -``` - -文章代码已经上传到 GitHub [Spring Boot Web开发 - 拦截处理](https://github.com/niumoo/springboot/tree/master/springboot-web-interceptor)。 -文章代码已经上传到 GitHub [Spring Boot Web开发 - Servlet,Filter,Listener](https://github.com/niumoo/springboot/tree/master/springboot-web-servlet-filter-listener)。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-07-web-exception.md b/docs/springboot/springboot-07-web-exception.md deleted file mode 100644 index 93472d8..0000000 --- a/docs/springboot/springboot-07-web-exception.md +++ /dev/null @@ -1,378 +0,0 @@ ---- -title: Springboot 系列(七)web 开发之异常错误处理机制剖析 -toc_number: false -date: 2019-02-22 08:00:01 -url: springboot/springboot-07-web-exception -tags: - - Springboot - - Springboot 异常处理 -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 前言 - -相信大家在刚开始体验 Springboot 的时候一定会经常碰到这个页面,也就是访问一个不存在的页面的默认返回页面。 -![Spring Boot 默认错误页面](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/992cd6d0f9f75737d1088523d80a64c1.png) - -如果是其他客户端请求,如接口测试工具,会默认返回JSON数据。 -```json -{ - "timestamp":"2019-01-06 22:26:16", - "status":404, - "error":"Not Found", - "message":"No message available", - "path":"/asdad" -} -``` -很明显,SpringBoot 根据 [HTTP 的请求头信息](https://www.wdbyte.com/2018/07/computer/protocol-http/)进行了不同的响应处理。 - -## 1. SpringBoot 异常处理机制 -追随 SpringBoot 源码可以分析出默认的错误处理机制。 -```java -// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration -// 绑定一些错误信息 记为 1 - @Bean - @ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT) - public DefaultErrorAttributes errorAttributes() { - return new DefaultErrorAttributes( - this.serverProperties.getError().isIncludeException()); - } -// 默认处理 /error 记为 2 - @Bean - @ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT) - public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) { - return new BasicErrorController(errorAttributes, this.serverProperties.getError(), - this.errorViewResolvers); - } -// 错误处理页面 记为3 - @Bean - public ErrorPageCustomizer errorPageCustomizer() { - return new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath); - } - @Configuration - static class DefaultErrorViewResolverConfiguration { - - private final ApplicationContext applicationContext; - - private final ResourceProperties resourceProperties; - - DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, - ResourceProperties resourceProperties) { - this.applicationContext = applicationContext; - this.resourceProperties = resourceProperties; - } -// 决定去哪个错误页面 记为4 - @Bean - @ConditionalOnBean(DispatcherServlet.class) - @ConditionalOnMissingBean - public DefaultErrorViewResolver conventionErrorViewResolver() { - return new DefaultErrorViewResolver(this.applicationContext, - this.resourceProperties); - } - - } - -``` -结合上面的注释,上面代码里的四个方法就是 Springboot 实现默认返回错误页面主要部分。 -### 1.1. errorAttributes -`errorAttributes`直译为错误属性,这个方法确实如此,直接追踪源代码。 -代码位于: -```java -// org.springframework.boot.web.servlet.error.DefaultErrorAttributes -``` -这个类里为错误情况共享很多错误信息,如。 -``` -errorAttributes.put("timestamp", new Date()); -errorAttributes.put("status", status); -errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); -errorAttributes.put("errors", result.getAllErrors()); -errorAttributes.put("exception", error.getClass().getName()); -errorAttributes.put("message", error.getMessage()); -errorAttributes.put("trace", stackTrace.toString()); -errorAttributes.put("path", path); -``` -这些信息用作共享信息返回,所以当我们使用模版引擎时,也可以像取出其他参数一样轻松取出。 -### 1.2. basicErrorControll -直接追踪 `BasicErrorController` 的源码内容可以发现下面的一段代码。 -```java -// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController -@Controller -// 定义请求路径,如果没有error.path路径,则路径为/error -@RequestMapping("${server.error.path:${error.path:/error}}") -public class BasicErrorController extends AbstractErrorController { - - // 如果支持的格式 text/html - @RequestMapping(produces = MediaType.TEXT_HTML_VALUE) - public ModelAndView errorHtml(HttpServletRequest request, - HttpServletResponse response) { - HttpStatus status = getStatus(request); - // 获取要返回的值 - Map model = Collections.unmodifiableMap(getErrorAttributes( - request, isIncludeStackTrace(request, MediaType.TEXT_HTML))); - response.setStatus(status.value()); - // 解析错误视图信息,也就是下面1.4中的逻辑 - ModelAndView modelAndView = resolveErrorView(request, response, status, model); - // 返回视图,如果没有存在的页面模版,则使用默认错误视图模版 - return (modelAndView != null) ? modelAndView : new ModelAndView("error", model); - } - - @RequestMapping - public ResponseEntity> error(HttpServletRequest request) { - // 如果是接受所有格式的HTTP请求 - Map body = getErrorAttributes(request, - isIncludeStackTrace(request, MediaType.ALL)); - HttpStatus status = getStatus(request); - // 响应HttpEntity - return new ResponseEntity<>(body, status); - } -} -``` -由上可知,`basicErrorControll` 用于创建用于请求返回的 `controller`类,并根据HTTP请求可接受的格式不同返回对应的信息,所以在使用浏览器和接口测试工具测试时返回结果存在差异。 -### 1.3. ererrorPageCustomizer -直接查看方法里的`new ErrorPageCustomizer(this.serverProperties, this.dispatcherServletPath);` -```java -//org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer - /** - * {@link WebServerFactoryCustomizer} that configures the server's error pages. - */ - private static class ErrorPageCustomizer implements ErrorPageRegistrar, Ordered { - - private final ServerProperties properties; - - private final DispatcherServletPath dispatcherServletPath; - - protected ErrorPageCustomizer(ServerProperties properties, - DispatcherServletPath dispatcherServletPath) { - this.properties = properties; - this.dispatcherServletPath = dispatcherServletPath; - } - // 注册错误页面 - // this.dispatcherServletPath.getRelativePath(this.properties.getError().getPath()) - @Override - public void registerErrorPages(ErrorPageRegistry errorPageRegistry) { - //getPath()得到如下地址,如果没有自定义error.path属性,则去/error位置 - //@Value("${error.path:/error}") - //private String path = "/error"; - ErrorPage errorPage = new ErrorPage(this.dispatcherServletPath - .getRelativePath(this.properties.getError().getPath())); - errorPageRegistry.addErrorPages(errorPage); - } - - @Override - public int getOrder() { - return 0; - } - - } - -``` -由上可知,当遇到错误时,如果没有自定义 `error.path` 属性,则请求转发至 `/error`. -### 1.4. conventionErrorViewResolver -根据上面的代码,一步步深入查看 SpringBoot 的默认错误处理实现,查看看 `conventionErrorViewResolver`方法。下面是 DefaultErrorViewResolver 类的部分代码,注释解析。 -```java -// org.springframework.boot.autoconfigure.web.servlet.error.DefaultErrorViewResolver - -// 初始化参数,key 是HTTP状态码第一位。 - static { - Map views = new EnumMap<>(Series.class); - views.put(Series.CLIENT_ERROR, "4xx"); - views.put(Series.SERVER_ERROR, "5xx"); - SERIES_VIEWS = Collections.unmodifiableMap(views); - } - @Override - public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, - Map model) { - // 使用HTTP完整状态码检查是否有页面可以匹配 - ModelAndView modelAndView = resolve(String.valueOf(status.value()), model); - if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { - // 使用 HTTP 状态码第一位匹配初始化中的参数创建视图对象 - modelAndView = resolve(SERIES_VIEWS.get(status.series()), model); - } - return modelAndView; - } - - - private ModelAndView resolve(String viewName, Map model) { - // 拼接错误视图路径 /eroor/[viewname] - String errorViewName = "error/" + viewName; - // 使用模版引擎尝试创建视图对象 - TemplateAvailabilityProvider provider = this.templateAvailabilityProviders - .getProvider(errorViewName, this.applicationContext); - if (provider != null) { - return new ModelAndView(errorViewName, model); - } - // 没有模版引擎,使用静态资源文件夹解析视图 - return resolveResource(errorViewName, model); - } - - private ModelAndView resolveResource(String viewName, Map model) { - // 遍历静态资源文件夹,检查是否有存在视图 - for (String location : this.resourceProperties.getStaticLocations()) { - try { - Resource resource = this.applicationContext.getResource(location); - resource = resource.createRelative(viewName + ".html"); - if (resource.exists()) { - return new ModelAndView(new HtmlResourceView(resource), model); - } - } - catch (Exception ex) { - } - } - return null; - } -``` -而 Thymeleaf 对于错误页面的解析实现。 -```java -//org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider -public class ThymeleafTemplateAvailabilityProvider - implements TemplateAvailabilityProvider { - @Override - public boolean isTemplateAvailable(String view, Environment environment, - ClassLoader classLoader, ResourceLoader resourceLoader) { - if (ClassUtils.isPresent("org.thymeleaf.spring5.SpringTemplateEngine", - classLoader)) { - String prefix = environment.getProperty("spring.thymeleaf.prefix", - ThymeleafProperties.DEFAULT_PREFIX); - String suffix = environment.getProperty("spring.thymeleaf.suffix", - ThymeleafProperties.DEFAULT_SUFFIX); - return resourceLoader.getResource(prefix + view + suffix).exists(); - } - return false; - } -} -``` -从而我们可以得知,错误页面首先会检查`模版引擎`文件夹下的 `/error/HTTP状态码` 文件,如果不存在,则检查去模版引擎下的`/error/4xx`或者 `/error/5xx` 文件,如果还不存在,则检查`静态资源`文件夹下对应的上述文件。 -## 2. 自定义异常页面 -经过上面的 SpringBoot 错误机制源码分析,知道当遇到错误情况时候,SpringBoot 会首先返回到`模版引擎`文件夹下的 `/error/HTTP`状态码 文件,如果不存在,则检查去模版引擎下的`/error/4xx`或者 `/error/5xx` 文件,如果还不存在,则检查`静态资源`文件夹下对应的上述文件。并且在返回时会共享一些错误信息,这些错误信息可以在模版引擎中直接使用。 -```java -errorAttributes.put("timestamp", new Date()); -errorAttributes.put("status", status); -errorAttributes.put("error", HttpStatus.valueOf(status).getReasonPhrase()); -errorAttributes.put("errors", result.getAllErrors()); -errorAttributes.put("exception", error.getClass().getName()); -errorAttributes.put("message", error.getMessage()); -errorAttributes.put("trace", stackTrace.toString()); -errorAttributes.put("path", path); -``` -因此,需要自定义错误页面,只需要在模版文件夹下的 error 文件夹下防止4xx 或者 5xx 文件即可。 -```html - - - - - - [[${status}]] - - - - -

-

错误码:[[${status}]]

-

信息:[[${message}]]

-

时间:[[${#dates.format(timestamp,'yyyy-MM-dd hh:mm:ss ')}]]

-

请求路径:[[${path}]]

-
- - - -``` -随意访问不存在路径得到。 -![Spring Boot 自定义错误页面](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6ceefac5c6b7a0a8c7ab800e718c033d.png) -发现错误页面已经跳转到我们的自定义页面。 -## 3. 自定义错误JSON -根据上面的 SpringBoot 错误处理原理分析,得知最终返回的 JSON 信息是从一个 map 对象中转换出来的,那么,只要能自定义 map 中的值,就可以自定义错误信息的 json 格式了。直接重写 `DefaultErrorAttributes`类的 `getErrorAttributes` 方法即可。 -```java -import org.springframework.boot.web.servlet.error.DefaultErrorAttributes; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.WebRequest; - -import java.util.HashMap; -import java.util.Map; - -/** - *

- * 自定义错误信息JSON值 - * - * @Author niujinpeng - * @Date 2019/1/7 15:21 - */ -@Component -public class ErrorAttributesCustom extends DefaultErrorAttributes { - - @Override - public Map getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { - Map map = super.getErrorAttributes(webRequest, includeStackTrace); - String code = map.get("status").toString(); - String message = map.get("error").toString(); - HashMap hashMap = new HashMap<>(); - hashMap.put("code", code); - hashMap.put("message", message); - return hashMap; - } -} -``` -使用 postman 请求测试。 -![Postman 测试结果](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1ba2dec88ba06b58011350b31a93e5e1.png) - -## 4. 统一异常处理 -使用 `@ControllerAdvice` 结合`@ExceptionHandler` 注解可以实现统一的异常处理,`@ExceptionHandler `注解的类会自动应用在每一个被 `@RequestMapping` 注解的方法。当程序中出现异常时会层层上抛 -```java -import lombok.extern.slf4j.Slf4j; -import net.codingme.boot.domain.Response; -import net.codingme.boot.enums.ResponseEnum; -import net.codingme.boot.utils.ResponseUtill; -import org.springframework.web.bind.annotation.ControllerAdvice; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.ResponseBody; - -import javax.servlet.http.HttpServletRequest; - -/** - *

- * 统一的异常处理 - * - * @Author niujinpeng - * @Date 2019/1/7 14:26 - */ - -@Slf4j -@ControllerAdvice -public class ExceptionHandle { - - @ResponseBody - @ExceptionHandler(Exception.class) - public Response handleException(Exception e) { - log.info("异常 {}", e); - if (e instanceof BaseException) { - BaseException exception = (BaseException) e; - String code = exception.getCode(); - String message = exception.getMessage(); - return ResponseUtill.error(code, message); - } - return ResponseUtill.error(ResponseEnum.UNKNOW_ERROR); - } -} -``` -请求异常页面得到响应如下。 -```json -{ - "code": "-1", - "data": [], - "message": "未知错误" -} -``` -文章代码已经上传到 GitHub [Spring Boot Web开发 - 错误机制](https://github.com/niumoo/springboot/tree/master/springboot-web-error)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-08-banner.md b/docs/springboot/springboot-08-banner.md deleted file mode 100644 index 3dde342..0000000 --- a/docs/springboot/springboot-08-banner.md +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Springboot 系列(八)动态Banner与图片转字符图案的手动实现 -toc_number: false -date: 2019-02-25 23:40:01 -url: springboot/springboot-08-banner -tags: - - Springboot - - Springboot Banner -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> 注意:本 Spring Boot 系列文章基于 Spring Boot 版本 **v2.1.1.RELEASE** 进行学习分析,版本不同可能会有细微差别。 - -![Springboot 启动 banner](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/33e6877404fed0daf8c894cec6a4d37c.png) - -使用过 Springboot 的对上面这个图案肯定不会陌生,Springboot 启动的同时会打印上面的图案,并带有版本号。查看官方文档可以找到关于 banner 的描述 ->The banner that is printed on start up can be changed by adding a banner.txt file to your classpath or by setting the spring.banner.location property to the location of such a file. If the file has an encoding other than UTF-8, you can set spring.banner.charset. In addition to a text file, you can also add a banner.gif, banner.jpg, or banner.png image file to your classpath or set the spring.banner.image.location property. Images are converted into an ASCII art representation and printed above any text banner. - - -就不翻译了,直接有道翻译贴过来看个大概意思。 ->可以通过向类路径中添加一个banner.txt文件或设置spring.banner来更改在start up上打印的banner。属性指向此类文件的位置。如果文件的编码不是UTF-8,那么可以设置spring.banner.charset。除了文本文件,还可以添加横幅。将gif、banner.jpg或banner.png图像文件保存到类路径或设置spring.banner.image。位置属性。图像被转换成ASCII艺术形式,并打印在任何文本横幅上面。 - -# 1. 自定义 banner -根据官方的描述,可以在类路径中自定义 banner 图案,我们进行尝试在放 resouce 目录下新建文件 banner.txt 并写入内容([在线字符生成](http://patorjk.com/software/taag/#p=testall&f=Graffiti&t=niumoo))。 -``` - (_) - _ __ _ _ _ _ __ ___ ___ ___ - | '_ \| | | | | '_ ` _ \ / _ \ / _ \ - | | | | | |_| | | | | | | (_) | (_) | - |_| |_|_|\__,_|_| |_| |_|\___/ \___/ 版本:${spring-boot.formatted-version} -``` -启动 Springboot 在控制台看到下面的输出。 -```log - (_) - _ __ _ _ _ _ __ ___ ___ ___ - | '_ \| | | | | '_ ` _ \ / _ \ / _ \ - | | | | | |_| | | | | | | (_) | (_) | - |_| |_|_|\__,_|_| |_| |_|\___/ \___/ 版本:(v2.1.3.RELEASE) -2019-02-25 14:00:31.289 INFO 12312 --- [ main] net.codingme.banner.BannerApplication : Starting BannerApplication on LAPTOP-L1S5MKTA with PID 12312 (D:\IdeaProjectMy\springboot-git\springboot-banner\target\classes started by Niu in D:\IdeaProjectMy\springboot-git\springboot-banner) -2019-02-25 14:00:31.291 INFO 12312 --- [ main] net.codingme.banner.BannerApplication : No active profile set, falling back to default profiles: default -2019-02-25 14:00:32.087 INFO 12312 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) -``` -发现自定义 banner 已经生效了,官方文档的介绍里说还可以放置图片,下面放置图片 banner.jpg 测试。 -网上随便找了一个图片。 -![Google Log](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/d9879377e30b5f37f9116e7927e35604.jpg)再次启动观察输出。 -![自定义 Banner](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/935928a0c76faa399a6f49252c713afa.png)Springboot 把图案转成了 ASCII 图案。 -# 2. ASCII 图案生成原理 -看了上面的例子,发现 Springboot 可以把图片转换成 ASCII 图案,那么它是怎么做的呢?我们或许可以想象出一个大概流程。 -1. 获取图片。 -2. 遍历图片像素点。 -3. 分析像素点,每个像素点根据颜色深度得出一个值,根据明暗度匹配不同的字符。 -4. 输出图案。 - -Springboot 对图片 banner 的处理到底是不是我们上面想想的那样呢?直接去源码中寻找答案。 -```java -/** 位置:org.springframework.boot.SpringApplicationBannerPrinter */ -//方法1: -public Banner print(Environment environment, Class sourceClass, Log logger) { - // 获取 banner 调用方法记为2 - Banner banner = getBanner(environment); - try { - logger.info(createStringFromBanner(banner, environment, sourceClass)); - } - catch (UnsupportedEncodingException ex) { - logger.warn("Failed to create String for banner", ex); - } - // 打印 banner - return new PrintedBanner(banner, sourceClass); -} -// 方法2 -private Banner getBanner(Environment environment) { - Banners banners = new Banners(); - // 获取图片banner,我们只关注这个,调用方法记为3 - banners.addIfNotNull(getImageBanner(environment)); - banners.addIfNotNull(getTextBanner(environment)); - if (banners.hasAtLeastOneBanner()) { - return banners; - } - if (this.fallbackBanner != null) { - return this.fallbackBanner; - } - return DEFAULT_BANNER; - } -// 方法3 -/** 获取自定义banner文件信息 */ -private Banner getImageBanner(Environment environment) { - // BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location"; - String location = environment.getProperty(BANNER_IMAGE_LOCATION_PROPERTY); - if (StringUtils.hasLength(location)) { - Resource resource = this.resourceLoader.getResource(location); - return resource.exists() ? new ImageBanner(resource) : null; - } - // IMAGE_EXTENSION = { "gif", "jpg", "png" }; - for (String ext : IMAGE_EXTENSION) { - Resource resource = this.resourceLoader.getResource("banner." + ext); - if (resource.exists()) { - return new ImageBanner(resource); - } - } - return null; -} -``` -上面是寻找自定义图片 banner 文件源码,如果把图片转换成 ASCII 图案继续跟进,追踪方法1中的`PrintedBanner(banner, sourceClass)`方法。最终查找输出图案的主要方法。 -```java -// 位置:org.springframework.boot.ImageBanner#printBanner -private void printBanner(BufferedImage image, int margin, boolean invert, - PrintStream out) { - AnsiElement background = invert ? AnsiBackground.BLACK : AnsiBackground.DEFAULT; - out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); - out.print(AnsiOutput.encode(background)); - out.println(); - out.println(); - AnsiColor lastColor = AnsiColor.DEFAULT; - // 图片高度遍历 - for (int y = 0; y < image.getHeight(); y++) { - for (int i = 0; i < margin; i++) { - out.print(" "); - } - // 图片宽度遍历 - for (int x = 0; x < image.getWidth(); x++) { - // 获取每一个像素点 - Color color = new Color(image.getRGB(x, y), false); - AnsiColor ansiColor = AnsiColors.getClosest(color); - if (ansiColor != lastColor) { - out.print(AnsiOutput.encode(ansiColor)); - lastColor = ansiColor; - } - // 像素点转换成字符输出,调用方法记为2 - out.print(getAsciiPixel(color, invert)); - } - out.println(); - } - out.print(AnsiOutput.encode(AnsiColor.DEFAULT)); - out.print(AnsiOutput.encode(AnsiBackground.DEFAULT)); - out.println(); - } -// 方法2,像素点转换成字符 - private char getAsciiPixel(Color color, boolean dark) { - // 根据 color 算出一个亮度值 - double luminance = getLuminance(color, dark); - for (int i = 0; i < PIXEL.length; i++) { - // 寻找亮度值匹配的字符 - if (luminance >= (LUMINANCE_START - (i * LUMINANCE_INCREMENT))) { - // PIXEL = { ' ', '.', '*', ':', 'o', '&', '8', '#', '@' }; - return PIXEL[i]; - } - } - return PIXEL[PIXEL.length - 1]; - } -``` -通过查看源码,发现 Springboot 的图片 banner 的转换和我们预想的大致一致,这么有趣的功能我们能不能自己写一个呢? - -# 3.自己实现图片转 ASCII字符 -根据上面的分析,总结一下思路,我们也可以手动写一个图片转 ASCII 字符图案。 -思路如下: -1. 图片大小缩放,调整到合适大小。 -2. 遍历图片像素。 -3. 获取图片像素点亮度(RGB颜色通过公式可以得到亮度数值)。 -4. 匹配字符。 -5. 输出图案。 - -上面的5个步骤直接使用 Java 代码就可以完整实现,下面是编写的源码。 -```java -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -import javax.imageio.ImageIO; - -/** - *

- * 根据图片生成字符图案 - * 1.图片大小缩放 - * 2.遍历图片像素点 - * 3.获取图片像素点亮度 - * 4.匹配字符 - * 5.输出图案 - * - * @author niujinpeng - * @website www.wdbyte.com - * @date 2019-02-25 23:03:01 - */ -public class GeneratorTextImage { - private static final char[] PIXEL = {'@', '#', '8', '&', 'o', ':', '*', '.', ' '}; - public static void main(String[] args) throws Exception { - // 图片缩放 - BufferedImage bufferedImage = makeSmallImage("src/main/resources/banner.jpg"); - // 输出 - printImage(bufferedImage); - } - - public static void printImage(BufferedImage image) throws IOException { - int width = image.getWidth(); - int height = image.getHeight(); - for (int i = 0; i < height; i++) { - for (int j = 0; j < width; j++) { - int rgb = image.getRGB(j, i); - Color color = new Color(rgb); - int red = color.getRed(); - int green = color.getGreen(); - int blue = color.getBlue(); - // 一个用于计算RGB像素点亮度的公式 - Double luminace = 0.2126 * red + 0.7152 * green + 0.0722 * blue; - double index = luminace / (Math.ceil(255 / PIXEL.length) + 0.5); - System.out.print(PIXEL[(int)(Math.floor(index))]); - } - System.out.println(); - } - } - - public static BufferedImage makeSmallImage(String srcImageName) throws Exception { - File srcImageFile = new File(srcImageName); - if (srcImageFile == null) { - System.out.println("文件不存在"); - return null; - } - FileOutputStream fileOutputStream = null; - BufferedImage tagImage = null; - Image srcImage = null; - try { - srcImage = ImageIO.read(srcImageFile); - int srcWidth = srcImage.getWidth(null);// 原图片宽度 - int srcHeight = srcImage.getHeight(null);// 原图片高度 - int dstMaxSize = 90;// 目标缩略图的最大宽度/高度,宽度与高度将按比例缩写 - int dstWidth = srcWidth;// 缩略图宽度 - int dstHeight = srcHeight;// 缩略图高度 - float scale = 0; - // 计算缩略图的宽和高 - if (srcWidth > dstMaxSize) { - dstWidth = dstMaxSize; - scale = (float)srcWidth / (float)dstMaxSize; - dstHeight = Math.round((float)srcHeight / scale); - } - srcHeight = dstHeight; - if (srcHeight > dstMaxSize) { - dstHeight = dstMaxSize; - scale = (float)srcHeight / (float)dstMaxSize; - dstWidth = Math.round((float)dstWidth / scale); - } - // 生成缩略图 - tagImage = new BufferedImage(dstWidth, dstHeight, BufferedImage.TYPE_INT_RGB); - tagImage.getGraphics().drawImage(srcImage, 0, 0, dstWidth, dstHeight, null); - return tagImage; - } finally { - if (fileOutputStream != null) { - try { - fileOutputStream.close(); - } catch (Exception e) { - } - fileOutputStream = null; - } - tagImage = null; - srcImage = null; - System.gc(); - } - } -} -``` -还是拿上面的 Google log 图片作为实验对象,运行得到字符图案输出。 -![图片转 ASCII 字符](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/ddc9487dd2050f9188825195427ed0a1.png) - -文章代码已经上传到 GitHub [Spring Boot](https://github.com/niumoo/springboot/tree/master/)。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-09-data-jdbc.md b/docs/springboot/springboot-09-data-jdbc.md deleted file mode 100644 index d1e75c8..0000000 --- a/docs/springboot/springboot-09-data-jdbc.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -title: Springboot 系列(九)使用 Spring JDBC 和 Druid 数据源监控 -toc_number: false -date: 2019-02-27 23:40:01 -url: springboot/springboot-09-data-jdbc -tags: - - Springboot - - Druid - - Spring JDBC -categories: - - Springboot -typora-root-url: ..\.. ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 前言 -![监控](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/129bdbc0a9f53e0fd3b748978ccd5fe6.png) - -作为一名 Java 开发者,相信对 JDBC(Java Data Base Connectivity)是不会陌生的,JDBC作为 Java 基础内容,它提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。下面演示下 Springboot 中如何使用 JDBC 操作,并配置使用 Druid 连接池,体验 Druid 对数据库操作强大的监控和扩展功能。Alibaba-Durid 官方手册[点这里](https://github.com/alibaba/druid/wiki/%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98)。 - - -## 1. 数据库准备 -使用mysql数据库创建数据库 springboot,并在库中新建数据表 user 并新增两条信息。 -```sql -CREATE TABLE `user` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `age` int(11) DEFAULT NULL, - `birthday` datetime DEFAULT NULL, - `password` varchar(32) NOT NULL, - `skills` varchar(255) DEFAULT NULL, - `username` varchar(32) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; - -# 新增数据 -INSERT INTO `springboot`.`user`(`id`, `age`, `birthday`, `password`, `skills`, `username`) VALUES (1, 17, '2019-01-12 21:02:30', '123', 'Go', 'Darcy'); -INSERT INTO `springboot`.`user`(`id`, `age`, `birthday`, `password`, `skills`, `username`) VALUES (3, 23, '2019-01-01 00:11:22', '456', 'Java', 'Chris'); -``` -## 2. 添加依赖 -新建一个 Springboot项目,这里不说。添加依赖如下。 -```xml - - - - org.springframework.boot - spring-boot-starter-jdbc - - - - - org.springframework.boot - spring-boot-starter-web - - - - - mysql - mysql-connector-java - runtime - - - - - com.alibaba - druid - 1.1.12 - - - - org.springframework.boot - spring-boot-starter-test - test - - - -``` -## 3. 配置数据源信息 -常规的 JDBC 配置不需要配置这么多内容,这里因为使用了 Druid 连接池,所以配置了 Druid 部分。对自动配置不理解的可以查看系列文章[Springboot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot02-config/#4-%E9%85%8D%E7%BD%AE%E7%9A%84%E4%BD%BF%E7%94%A8)。 -```yml -spring: - datasource: - username: root - password: 123 - url: jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8 - driver-class-name: com.mysql.jdbc.Driver - type: com.alibaba.druid.pool.DruidDataSource - - initialSize: 5 - minIdle: 5 - maxActive: 20 - maxWait: 60000 - timeBetweenEvictionRunsMillis: 60000 - minEvictableIdleTimeMillis: 300000 - validationQuery: SELECT 1 FROM DUAL - testWhileIdle: true - testOnBorrow: false - testOnReturn: false - poolPreparedStatements: true - # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙 - filters: stat - maxPoolPreparedStatementPerConnectionSize: 20 - useGlobalDataSourceStat: true - connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 -``` -配置完毕之后,配置信息还不能绑定到 Druid数据源中,还需要新建一个配置类绑定数据源和配置信息。 -```java -/** - *

- * Druid 数据源配置 - * - * @Author niujinpeng - * @Date 2019/1/14 22:20 - */ -@Configuration -public class DruidConfig { - /** - * 配置绑定 - * @return - */ - @Bean - @ConfigurationProperties(prefix = "spring.datasource") - public DruidDataSource druid() { - return new DruidDataSource(); - } -} -``` -到这里,数据源已经配置完毕,编写测试方法测试 druid 连接池是否生效。 -```java - -@RunWith(SpringRunner.class) -@SpringBootTest -public class SpringbootDataJdbcApplicationTests { - @Autowired - DataSource dataSource; - /** - * 测试JDBC数据源 - * @throws SQLException - */ - @Test - public void contextLoads() throws SQLException { - System.out.println(dataSource.getClass()); - Connection connection = dataSource.getConnection(); - System.out.println(connection); - connection.close(); - } -} -``` -运行看到 contextLoads 输出信息。 -``` -class com.alibaba.druid.pool.DruidDataSource -Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary. -2019-02-27 14:14:56.144 INFO 12860 --- [ main] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited -com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@3e104d4b -``` -输出日志中的 com.alibaba.druid 说明 Druid 已经生效。 -## 4. 使用 Spring-JDBC -传统的 JDBC 使用中,需要编写大量代码,从构造 PreparedStatement 到查询不胜其烦。面对这样的开发痛点,Spring 封装了 Spring-jdbc. 让我们使用 JdbcTemplate 即可轻松的操作数据库。Spring-jdbc 的详细使用不是这篇文章重点,只简单演示下是否生效。 -编写控制器,查询一个 user 信息。 -```java -@RestController -public class JdbcController { - @Autowired - JdbcTemplate jdbcTemplate; - @ResponseBody - @GetMapping("/query") - public Map map() { - List> list = jdbcTemplate.queryForList("select * FROM user"); - return list.get(0); - } -} -``` -启动spring 项目,请求 /query 接口得到正常响应。 -```json -{ -"id": 1, -"age": 17, -"birthday": "2019-01-12T13:02:30.000+0000", -"password": "123", -"skills": "Go", -"username": "Darcy" -} -``` -可见 Spring-JDBC 已经从数据库中取出了数据信息。 -## 5. 使用 Druid 监控 -如果使用 Druid 连接池却不使用监控功能,那么就有点暴殄天物了。下面开始配置 Druid 的 SQL 监控功能。在上面写的 DruidConfig 配置类中增加配置 Druid 的 Servlet 和 Filter. -```java - /** - * Druid的servlet - * @return - */ - @Bean - public ServletRegistrationBean statViewServlet() { - ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet()); - Map initParams = new HashMap<>(); - initParams.put("loginUsername", "admin"); - initParams.put("loginPassword", "123"); - initParams.put("allow","127.0.0.1"); - bean.setInitParameters(initParams); - bean.setUrlMappings(Arrays.asList("/druid/*")); - return bean; - } - @Bean - public FilterRegistrationBean webStatFilter() { - FilterRegistrationBean bean = new FilterRegistrationBean<>(new WebStatFilter()); - HashMap initParams = new HashMap<>(); - initParams.put("exclusions", "/css,/druid/*"); - bean.setInitParameters(initParams); - bean.setUrlPatterns(Arrays.asList("/*")); - return bean; - } -``` -上面配置了 Druid 监控访问路径为 `/druid`、登录用户是 `admin`、登录密码是`123`、允许访问的IP是`127.0.0.1` 本机、不需要监控的请求是 `/css` 和 `/druid` 开头的请求。 - -重新启动项目,访问测试 `/query`,然后访问 `/durid` 登录页。 -![Druid 登录页](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/d6f42164708fcd4c8e5009386bb9837e.png) - -登录后可以看到 SQL 监控信息和 URL 监控等信息。 -![SQL 监控](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/64a71b68b6afb74e22a2eb274e8956c3.png) -URL 监控。 -![URL 监控](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7b96df5bbd8698295696a3805ad82e26.png) - -文章代码已经上传到 GitHub [Spring Boot jdbc](https://github.com/niumoo/springboot/tree/master/springboot-data-jdbc)。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-10-data-jpa.md b/docs/springboot/springboot-10-data-jpa.md deleted file mode 100644 index 72f4b1c..0000000 --- a/docs/springboot/springboot-10-data-jpa.md +++ /dev/null @@ -1,343 +0,0 @@ ---- -title: Springboot 系列(十)使用 Spring data jpa 访问数据库 -toc_number: false -date: 2019-03-01 01:40:01 -url: springboot/springboot-10-data-jpa -tags: - - Springboot - - SpringData Jpa -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![桌面生活(来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/7bea43f15edb0db3ac708c346acd35c4.jpg) - -## 前言 -Springboot data jpa 和 Spring jdbc 同属于 Spring开源组织,在 Spring jdbc 之后又开发了持久层框架,很明显 Spring data jpa 相对于 Spring jdbc 更加的便捷强大,不然也就没有开发的必要了。根据下面的文章开始体验下 Spring data jpa 魅力。 - -## 1. Spring data jpa 介绍 -Spring data jpa 是 Spring data 系列的一部分,使用它可以轻松的实现对数据访问层的增强支持,在相当长的一段时间内,实现应用程序的数据访问层一直很麻烦,需要编写大量的样板式的代码来执行简单查询或者分页操作。Spring data jpa 的目标是尽量的减少实际编码来改善数据访问层的操作。 -## 2. Spring data jpa 依赖 -这次的实验基于系列文章第九篇实验代码,代码中的数据源相关的配置也可以参考系列文章第九篇,这里只演示 Spring data jpa 部分。 - -创建Spring boot 项目,引入需要的依赖。 -```xml - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.alibaba - fastjson - 1.2.47 - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.junit.jupiter - junit-jupiter-api - RELEASE - compile - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - - mysql - mysql-connector-java - - - - - com.alibaba - druid-spring-boot-starter - 1.1.10 - - -``` -## 3. Spring data jpa 配置 -关于 Druid 数据源的配置不再说明,可以参考系列文章第九篇。 -```properties -############################################################ -# 服务启动端口号 -server.port=8080 -spring.profiles.active=dev -############################################################ -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8 -spring.datasource.driver-class-name= com.mysql.jdbc.Driver -spring.datasource.username=root -spring.datasource.password=123 -# 使用 druid 数据源 -spring.datasource.type: com.alibaba.druid.pool.DruidDataSource -spring.datasource.initialSize: 5 -spring.datasource.minIdle: 5 -spring.datasource.maxActive: 20 -spring.datasource.maxWait: 60000 -spring.datasource.timeBetweenEvictionRunsMillis: 60000 -spring.datasource.minEvictableIdleTimeMillis: 300000 -spring.datasource.validationQuery: SELECT 1 FROM DUAL -spring.datasource.testWhileIdle: true -spring.datasource.testOnBorrow: false -spring.datasource.testOnReturn: false -spring.datasource.poolPreparedStatements: true -spring.datasource.filters: stat -spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20 -spring.datasource.useGlobalDataSourceStat: true -spring.datasource.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 -# SpringBoot JPA -spring.jpa.show-sql=true -# create 每次都重新创建表,update,表若存在则不重建 -spring.jpa.hibernate.ddl-auto=update -spring.jpa.database-platform=org.hibernate.dialect.MySQL55Dialect -``` -`spring.jpa.show-sql=true` 打印 SQL 语句。 -`spring.jpa.hibernate.ddl-auto=update` 根据 Enity 自动创建数据表,Update 表示如果表存在则不重新创建。 - -## 4. Spring data jpa 编码 -Springboot Data JPA 是 ORM 的完整实现,实体类和数据表关系一一对应,因此实体类也就是数据表结构。`spring.jpa.hibernate.ddl-auto=update` 会在 JPA 运行时自动在数据表中创建被 `@Entity` 注解的实体数据表。如果表已经存在,则不会创建。 -### 4.1. 数据实体类 -```java -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; - -import javax.persistence.*; -import javax.validation.constraints.NotNull; -import java.util.Date; - -/** - *

- * - * @Entity JPA实体 - * @Data GET SET TOSTRING - * @NoArgsConstructor 无参构造 - * @AllArgsConstructor 全参构造 - * @Author niujinpeng - * @Date 2018/12/19 17:13 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@Entity -@Table(name = "user") -public class User { - - /** - * 用户ID - * - * @Id 主键 - * @GeneratedValue 自增主键 - */ - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Integer id; - - /** - * 用户名 - */ - @Column(name = "username", length = 32, nullable = false) - @NotNull(message = "用户名不能为空") - private String username; - /** - * 密码 - */ - @Column(name = "password", length = 32, nullable = false) - @NotNull(message = "密码不能为空") - private String password; - /** - * 年龄 - */ - @Column(name = "age", length = 3) - private Integer age; - /** - * 生日 - */ - @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") - private Date birthday; - /** - * 技能 - */ - private String skills; -} -``` - -### 4.2. JPA 操作接口 -JPA 操作接口只需要继承 JpaRepository 就可以了,JpaRepository 里封装了常用的增删改查分页等方法,可以直接使用,如果需要自定义查询方式,可以通过构造方法名的方式增加。下面增加了一个根据 username 和 password 查询 User 信息的方法。 - -```java -package net.codingme.boot.domain.repository; - -import net.codingme.boot.domain.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -/** - *

- * - * @Author niujinpeng - * @Date 2019/1/1114:26 - */ -@Repository -public interface UserRepository extends JpaRepository { - /** - * 一个自定义方法,根据 username 和 password 查询 User 信息 - */ - User findByUsernameAndPassword(String username, String password); -} -``` -到这里,Jpa 的功能已经可以测试使用了,关于 Service 层和 Controller 就不在这里贴了,直接编写 Springboot 单元测试进行 Jpa 测试。 - -## 5. Spring data jpa 测试 -使用 Springboot 的单元测试方法可以方便的测试 Springboot 项目,对 Springboot 单元测试不了解的可以直接参照[官方文档](https://docs.spring.io/spring-boot/docs/2.1.x/reference/htmlsingle/#boot-features-testing-spring-applications)的说明,当然,也可以直接看下面的示例代码。 -下面编写四个测试方法分别测试根据 Id 查询、分页查询、更新数据、根据 username 和 password 查询四个功能。 -```java -package net.codingme.boot.domain.repository; - -import net.codingme.boot.domain.User; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; -import org.springframework.test.context.junit4.SpringRunner; -import java.util.List; -import java.util.Optional; - -/** - * 单元测试 - */ -@RunWith(SpringRunner.class) -@SpringBootTest -public class UserRepositoryTest { - - @Autowired - private UserRepository userRepository; - - /** - * id查询 - */ - @Test - public void findByIdUserTest() { - Optional userOptional = userRepository.findById(1); - User user = userOptional.orElseGet(null); - System.out.println(user); - Assert.assertNotNull(user); - } - - /** - * 分页查询 - */ - @Test - public void findByPageTest() { - PageRequest pageRequest = PageRequest.of(0, 2); - Page userPage = userRepository.findAll(pageRequest); - List userList = userPage.getContent(); - userList.forEach((user) -> System.out.println(user)); - Assert.assertNotNull(userList); - } - - /** - * 更新 - */ - @Test - public void updateUserTest() { - Optional userOptional = userRepository.findById(1); - User user = userOptional.orElseThrow(() -> new RuntimeException("用户信息没有取到")); - System.out.println(user.getAge()); - ; - user.setAge(user.getAge() + 1); - User updateResult = userRepository.save(user); - Assert.assertNotNull(updateResult); - } - - /** - * 根据 Username 和 Password 查询 - */ - @Test - public void findByUsernameAndPasswordTest() { - User user = userRepository.findByUsernameAndPassword("Darcy", "123"); - System.out.println(user); - Assert.assertNotNull(user); - } -} -``` -首先看到四个方法全部运行通过。 -![单元测试结果](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/77d538a467dd500356c519b67f41b7a6.png) -分页查询查出数据库中的两条数据。 -```log -Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.password as password4_0_, user0_.skills as skills5_0_, user0_.username as username6_0_ from user user0_ limit ? -Hibernate: select count(user0_.id) as col_0_0_ from user user0_ -User(id=1, username=Darcy, password=123, age=18, birthday=2019-01-12 21:02:30.0, skills=Go) -User(id=3, username=Chris, password=456, age=23, birthday=2019-01-01 00:11:22.0, skills=Java) -``` -根据 Id 查询也没有问题。 -``` -Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? -User(id=1, username=Darcy, password=123, age=18, birthday=2019-01-12 21:02:30.0, skills=Go) -``` -更新操作也是正常输出 SQL ,没有任何异常。 -``` -Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? -18 -Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.birthday as birthday3_0_0_, user0_.password as password4_0_0_, user0_.skills as skills5_0_0_, user0_.username as username6_0_0_ from user user0_ where user0_.id=? -Hibernate: update user set age=?, birthday=?, password=?, skills=?, username=? where id=? -``` -最后一个是自定义查询操作,上面三个方法的输出中,Darcy 用户对应的年龄是 18,在经过更新加1 之后应该变为19,下面是自定义查询的结果。 -``` -Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.birthday as birthday3_0_, user0_.password as password4_0_, user0_.skills as skills5_0_, user0_.username as username6_0_ from user user0_ where user0_.username=? and user0_.password=? -User(id=1, username=Darcy, password=123, age=19, birthday=2019-01-12 21:02:30.0, skills=Go) -``` -可见是没有任何问题的。 -文章代码已经上传到 [GitHub](https://github.com/niumoo/springboot/tree/master/springboot-data-jpa)。 -测试代码中使用了一些 JDK8 的特性,如 `Optional` 类的使用,以后会单独写一部分关于 JDK 新特性的文章,欢迎扫码关注公众号。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-11-data-mybatis.md b/docs/springboot/springboot-11-data-mybatis.md deleted file mode 100644 index a166bee..0000000 --- a/docs/springboot/springboot-11-data-mybatis.md +++ /dev/null @@ -1,370 +0,0 @@ ---- -title: Springboot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库 -toc_number: false -date: 2019-03-07 01:40:01 -url: springboot/springboot-11-data-mybatis -tags: - - Springboot - - Mybatis - - 插件 -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![桌面生活(来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/60327e81c9b1f06ed84cb177feded21b.jpg) - -## 1. Springboot mybatis 介绍 -MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数获取结果集的过程。MyBatis 可以使用简单的 ``XML`` 或``注解``来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。 - -关于 Mybatis 的基础知识可以查询官方文档,十分的详细。[mybatis 官方文档](http://www.mybatis.org/mybatis-3/zh/getting-started.html). -## 2. Springboot mybatis 依赖 -本系列 Springboot 文章主要是 Springboot 的学习与分析,也因此只会试验 Mybatis 在 Springboot 中的一些用法,关于 Mybatis 的基础知识,还是需要自行学习的。 -创建 Springboot 项目不提,引入 maven 依赖,主要是 mybastis 核心依赖以及一个 mybatis mapper 自动生成插件。依赖中的 druid 数据源部分,可以参考系列文章第九篇。 -```xml - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.alibaba - fastjson - 1.2.47 - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - org.junit.jupiter - junit-jupiter-api - RELEASE - compile - - - - - com.alibaba - druid-spring-boot-starter - 1.1.10 - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.3.2 - - - - org.mybatis.generator - mybatis-generator-core - 1.3.7 - compile - true - - - - - mysql - mysql-connector-java - - -``` - -## 3. Springboot mybatis 配置 -关于 Druid 数据源的配置不再说明,可以参考系列文章第九篇。配置中主要配置了项目编码、数据源信息、durid 数据源和 mybatis 的 mapper 位置以及 mybatis 映射别名的包路径。 -```properties -############################################################ -# 服务启动端口号 -server.port=8080 -spring.profiles.active=dev - -# 编码 -server.tomcat.uri-encoding=utf-8 -spring.http.encoding.force=true -spring.http.encoding.charset=UTF-8 -spring.http.encoding.enabled=true -############################################################ -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8 -spring.datasource.driver-class-name= com.mysql.jdbc.Driver -spring.datasource.username=root -spring.datasource.password=123 - -# 使用 druid 数据源 -spring.datasource.type: com.alibaba.druid.pool.DruidDataSource -spring.datasource.initialSize: 5 -spring.datasource.minIdle: 5 -spring.datasource.maxActive: 20 -spring.datasource.maxWait: 60000 -spring.datasource.timeBetweenEvictionRunsMillis: 60000 -spring.datasource.minEvictableIdleTimeMillis: 300000 -spring.datasource.validationQuery: SELECT 1 FROM DUAL -spring.datasource.testWhileIdle: true -spring.datasource.testOnBorrow: false -spring.datasource.testOnReturn: false -spring.datasource.poolPreparedStatements: true -spring.datasource.filters: stat -spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20 -spring.datasource.useGlobalDataSourceStat: true -spring.datasource.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 - -# mybatis -mybatis.mapper-locations=classpath:mapper/*.xml -mybatis.type-aliases-package=net.codingme.boot.domain - -``` -## 4. Springboot mybatis 编码 -mybatis 是半 ORM 框架,它通过 XML 描述符或者注解把 POJO 对象与 SQL 信息关联起来,也因为是和 SQL 关联起来,使用 mybatis 可以充分的利用数据的各种功能以及强大的 SQL 语句。也可以发发现使用 mybatis 至少应该建立 POJO 对象和 SQL 关联信息以及编写相关操作代码。 -### 4.1. 数据库准备 -既然是持久层框架,先准备一个用于实验操作的数据表。上一个步骤中有配置数据库信息为 springboot。 -```properties -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot -``` -因此在 mysql 数据库的 springboot 库中创建表 book 用于演示。 -```sql -CREATE TABLE `book` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `author` varchar(255) DEFAULT NULL COMMENT '书籍作者', - `name` varchar(255) DEFAULT NULL COMMENT '书籍名称', - `price` float NOT NULL COMMENT '书籍价格', - `create_time` datetime NOT NULL COMMENT '创建时间', - `description` varchar(255) DEFAULT NULL COMMENT '书籍描述', - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8; -``` -增加测试数据。 -``` -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (2, '金庸', '笑傲江湖', 12, '2018-09-01 10:10:12', '是作家金庸创作的一部长篇武侠小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (3, '罗贯中', '三国演义', 22, '2018-09-01 10:10:16', '是作家罗贯中创作的一部长篇历史小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (4, '吴承恩', '西游记', 17, '2018-09-01 10:10:19', '是作家吴承恩创作的一部长篇小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (5, '金庸1535767819284', '笑傲江湖1535767819284', 43, '2018-09-01 10:10:19', '是作家金庸创作的一部长篇武侠小说1535767819284'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (6, '金庸1535767819679', '笑傲江湖1535767819679', 24, '2018-09-01 10:10:20', '是作家金庸创作的一部长篇武侠小说1535767819679'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (7, '罗贯中1535769035138', '三国演义1535769035138', 20, '2018-09-01 10:30:35', '是罗贯中创作的一部小说1535769035138'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (8, '金庸1535783611785', '笑傲江湖1535783611785', 30, '2018-09-01 14:33:32', '是作家金庸创作的一部长篇武侠小说1535783611785'); -``` -### 4.2. 自动生成插件 -传统的 mybatis 开发过程需要依照数据表新建大量的 POJO 类,然后在编写响应的增删改查接口,继而编写增删改查对应的 XML 文件。过程无趣且有重复劳动,因此产生了一个自动生成工具,可以通过 JDBC 连接到数据库,自动的创建 POJO、操作接口、XML 文件。 - -在引入依赖的时候已经引入了自动生成插件,也就是 `mybatis-generator-core`。 - -接着在项目根目录下创建自动生成配置文件,主要配置数据库信息和要生成的表已经生成的代码存放位置。 -![项目结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/9de1c6553d3afc7f006f791fdf088b04.png) - -在之前作者也介绍过,可以参考博客文章[使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/)。 - -```xml - - - - - - - - - - - - - - - - - - - - -
- -
-
-``` - -写好配置文件之后,还需要写一个启动程序,用于加载配置文件,运行就可以生成相关配置。 -```java -import org.mybatis.generator.api.MyBatisGenerator; -import org.mybatis.generator.config.Configuration; -import org.mybatis.generator.config.xml.ConfigurationParser; -import org.mybatis.generator.internal.DefaultShellCallback; - -import java.io.File; -import java.util.ArrayList; - -/** - *

- * Mybatis generator的逆向生成工具类 - * - * @Author niujinpeng - */ -public class MybatisGenerator { - - public void generator() throws Exception { - ArrayList warnings = new ArrayList<>(); - boolean overwrite = true; - // 指定你想工程配置文件 - File configFile = new File("generatorConfig.xml"); - System.out.println(configFile.getAbsolutePath()); - ConfigurationParser cp = new ConfigurationParser(warnings); - Configuration config = cp.parseConfiguration(configFile); - DefaultShellCallback callback = new DefaultShellCallback(overwrite); - MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); - myBatisGenerator.generate(null); - } - - public static void main(String[] args) throws Exception { - MybatisGenerator mybatisGenerator = new MybatisGenerator(); - mybatisGenerator.generator(); - } -} -``` -生成的文件如下图。 - -![项目结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/de3a01eaf435ed5ab223603a04c9eb71.png) - -查看生成的接口以及 XML 映射文件可以发现已经自动生成了常用的几个方法。 -1. deleteByPrimaryKey -1. insert -1. updateByPrimaryKey -1. selectByPrimaryKey -1. selectAll - -生成完成之后要在 Springboot 启动器上添加 MapperScan 注解指定要扫描的 mapper 位置。 -```java -@SpringBootApplication -@MapperScan("net.codingme.boot.domain.mapper") -public class BootApplication { - - public static void main(String[] args) { - SpringApplication.run(BootApplication.class, args); - } -} -``` - -### 4.3. 注解配置方式 -Mybatis 同样支持注解的方式配置映射关系,使用注解可以替代 XML 的配置,写一个简单的注解例子。在刚才生成的 BookMapper.java 中增加一个根据作者名称查询的方法,并映射字段对应的属性。 -```java -// 添加 @Repository 注解,这样在使用 @Autowired 引入的时候不会报横线 -@Repository -public interface BookMapper { - /** - * 注解方式配置映射 - * - * @param author - * @return - * @Results 字段和属性映射关系 - * @Select 查询语句 - */ - @Results({ - @Result(property = "id", column = "ids"), - @Result(property = "name", column = "name"), - @Result(property = "author", column = "authors"), - @Result(property = "createTime", column = "create_time") - }) - @Select("select id as ids, author as authors, name, price, create_time, description from book where author = #{author}") - List selectByAuthor(@Param("author") String author); - // 省略下面自动生成代码 -``` -## 5. Springboot mybatis 测试 -正常情况下会在项目中的业务层 service 包下创建接口和类然后通过注解引入使用。 -```java -@Autowired -private BookMapper bookMapper; -``` -我们只是实验,没有这样写一套的必要,只要能确保 BookMapper 可以正常注入使用就好了。因此创建测试类进行测试。 -![创建测试类](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/730d20139c46c077d4064261ec9290a5.png) - -在生成的(也可以完全手写测试方法)测试类中添加测试方法进行测试。 -```java -@RunWith(SpringRunner.class) -@SpringBootTest -public class BookMapperTest { - - @Autowired - private BookMapper bookMapper; - - @Test - public void testSelectAll() { - List bookList = bookMapper.selectAll(); - Assert.assertNotNull(bookList); - bookList.forEach((book) -> System.out.println(book)); - } - - - @Test - public void testSelectByAuthro() { - List bookList = bookMapper.selectByAuthor("金庸"); - Assert.assertNotNull(bookList); - bookList.forEach((book) -> System.out.println(book)); - } - - @Test - public void testSelectByPrimaryKey() { - Book book = bookMapper.selectByPrimaryKey(2); - Assert.assertNotNull(book); - System.out.println(book); - } - - public void testDeleteByPrimaryKey() { - int primaryKey = bookMapper.deleteByPrimaryKey(8); - Assert.assertNotEquals(0, primaryKey); - System.out.println(primaryKey); - } - -} -``` -为了观察查询接口 book 的信息输出,重写 Book 类的 toString 方法,然后运行单元测试。 - -![单元测试结果](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/0a059a1c7f49265a61cda7bbd07f1d2d.png) - -可以发现测试全部通过。结果正常。 -文章代码已经上传到 Github [Spring Boot 连接数据库 - Mybatis](https://github.com/niumoo/springboot/tree/master/springboot-data-mybatis)。 - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-12-data-mybatis-page.md b/docs/springboot/springboot-12-data-mybatis-page.md deleted file mode 100644 index 8623729..0000000 --- a/docs/springboot/springboot-12-data-mybatis-page.md +++ /dev/null @@ -1,500 +0,0 @@ ---- -title: Springboot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件 -toc_number: true -date: 2019-03-08 00:40:22 -url: springboot/springboot-12-data-mybatis-page -tags: - - Springboot - - Mybatis - - 插件 - - Mybatis pagehelper -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![桌面生活(来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/64cccddf32b7e3f4069ca2459057496e.png) -## 前言 -在 Springboot 系列文章第十一篇里([使用 Mybatis(自动生成插件) 访问数据库](https://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483756&idx=1&sn=902dba8ed665131453bc26de246ccaa1&chksm=e984e808def3611edf0949a9db71ea6dc29432b25fb27015d78bc8117a50a3bedd79194de5ea&token=93558379&lang=zh_CN#rd)),实验了 Springboot 结合 Mybatis 以及 Mybatis-generator 生成插件的开发过程,其实对于 Mybatis 来讲还有很多优秀方便好用的插件,比如这次要演示的 通用 Mapper 生成插件和分页插件。 - - -## 数据库准备 - 既然是持久层框架,先准备一个用于实验操作的数据表,这次还是使用上一个实验使用的 mysql 数据库中的 springboot.book 数据表。 - -未创建的可以在 mysql 数据库的 springboot 库中创建表 book 用于演示。 -```sql -CREATE TABLE `book` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `author` varchar(255) DEFAULT NULL COMMENT '书籍作者', - `name` varchar(255) DEFAULT NULL COMMENT '书籍名称', - `price` float NOT NULL COMMENT '书籍价格', - `create_time` datetime NOT NULL COMMENT '创建时间', - `description` varchar(255) DEFAULT NULL COMMENT '书籍描述', - PRIMARY KEY (`id`) -) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8; -``` -增加测试数据。 -``` -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (2, '金庸', '笑傲江湖', 12, '2018-09-01 10:10:12', '是作家金庸创作的一部长篇武侠小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (3, '罗贯中', '三国演义', 22, '2018-09-01 10:10:16', '是作家罗贯中创作的一部长篇历史小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (4, '吴承恩', '西游记', 17, '2018-09-01 10:10:19', '是作家吴承恩创作的一部长篇小说'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (5, '金庸1535767819284', '笑傲江湖1535767819284', 43, '2018-09-01 10:10:19', '是作家金庸创作的一部长篇武侠小说1535767819284'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (6, '金庸1535767819679', '笑傲江湖1535767819679', 24, '2018-09-01 10:10:20', '是作家金庸创作的一部长篇武侠小说1535767819679'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (7, '罗贯中1535769035138', '三国演义1535769035138', 20, '2018-09-01 10:30:35', '是罗贯中创作的一部小说1535769035138'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (9, '金庸1535783613226', '笑傲江湖1535783613226', 30, '2018-09-01 14:33:33', '是作家金庸创作的一部长篇武侠小说1535783613226'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (10, '金庸1535783618455', '笑傲江湖1535783618455', 30, '2018-09-01 14:33:38', '是作家金庸创作的一部长篇武侠小说1535783618455'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (11, '金庸1535783620634', '笑傲江湖1535783620634', 30, '2018-09-01 14:33:41', '是作家金庸创作的一部长篇武侠小说1535783620634'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (12, '金庸1535783672457', '笑傲江湖1535783672457', 30, '2018-09-01 14:34:32', '是作家金庸创作的一部长篇武侠小说1535783672457'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (13, '金庸1535783673664', '笑傲江湖1535783673664', 30, '2018-09-01 14:34:34', '是作家金庸创作的一部长篇武侠小说1535783673664'); -INSERT INTO `springboot`.`book`(`id`, `author`, `name`, `price`, `create_time`, `description`) VALUES (14, '金庸1535783939262', '笑傲江湖1535783939262', 30, '2018-09-01 14:38:59', '是作家金庸创作的一部长篇武侠小说1535783939262'); -``` -## 引入依赖 -创建 Springboot 项目不提,引入 maven 依赖,主要是 mybastis 核心依赖以及 mybatis mapper 自动生成插件、分页插件、通用 Mapper 插件。依赖中的 druid 数据源部分,可以参考系列文章第九篇。 -```xml - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - com.alibaba - fastjson - 1.2.47 - - - - - org.projectlombok - lombok - true - - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - - mysql - mysql-connector-java - - - - - com.alibaba - druid-spring-boot-starter - 1.1.10 - - - - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.3.2 - - - - - org.mybatis.generator - mybatis-generator-core - 1.3.7 - compile - true - - - - - com.github.pagehelper - pagehelper-spring-boot-starter - 1.2.10 - - - tk.mybatis - mapper-spring-boot-starter - 2.1.5 - - - - - tk.mybatis - mapper - 4.0.4 - - - -``` -简单说明一下几个不常见依赖的作用。 -1. mybatis-generator-core 用于自动生成 model、mapper 接口、mapper xml。 -1. pagehelper-spring-boot-starter 用于分页 -1. mapper 用于增强增删改查功能,集成了很多常用操作。 - -## 增加配置 -关于 Druid 数据源的配置不再说明,可以参考系列文章第九篇。配置中主要配置了项目编码、数据源信息、durid 数据源和 mybatis 的 mapper 位置以及 mybatis 映射别名的包路径。还有 pagehelper 分页插件部分。 - -```properties -############################################################ -# 服务启动端口号 -server.port=8080 -spring.profiles.active=dev -# 编码 -server.tomcat.uri-encoding=utf-8 -spring.http.encoding.force=true -spring.http.encoding.charset=UTF-8 -spring.http.encoding.enabled=true -############################################################ -spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot?characterEncoding=utf-8&serverTimezone=GMT%2B8 -spring.datasource.driver-class-name=com.mysql.jdbc.Driver -spring.datasource.username=root -spring.datasource.password=123 -# 使用 druid 数据源 -spring.datasource.type:com.alibaba.druid.pool.DruidDataSource -spring.datasource.initialSize:5 -spring.datasource.minIdle:5 -spring.datasource.maxActive:20 -spring.datasource.maxWait:60000 -spring.datasource.timeBetweenEvictionRunsMillis:60000 -spring.datasource.minEvictableIdleTimeMillis:300000 -spring.datasource.validationQuery:SELECT 1 FROM DUAL -spring.datasource.testWhileIdle:true -spring.datasource.testOnBorrow:false -spring.datasource.testOnReturn:false -spring.datasource.poolPreparedStatements:true -spring.datasource.filters:stat -spring.datasource.maxPoolPreparedStatementPerConnectionSize:20 -spring.datasource.useGlobalDataSourceStat:true -spring.datasource.connectionProperties:druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 -############################################################ -# mybatis -mybatis.mapper-locations=classpath:mapper/*.xml -mybatis.type-aliases-package=net.codingme.boot.domain -############## mybatis page配置####################### -# restart 类加载加载 include 进去的 jar 包。 -restart.include.mapper=/mapper-[\\w-\\.]+jar -restart.include.pagehelper=/pagehelper-[\\w-\\.]+jar -# mappers 多个接口时逗号隔开 -mapper.mappers=net.codingme.boot.util.MybatisMapper -mapper.not-empty=false -mapper.identity=MYSQL -# pagehelper -pagehelper.helperDialect=mysql -pagehelper.reasonable=true -pagehelper.supportMethodsArguments=true -pagehelper.params=count=countSql -# 输出 mybatis SQL 日志 -logging.level.net.codingme.boot.domain.mapper=debug -``` -一些说明。 -1. `mapper.mappers=net.codingme.boot.util.MybatisMapper` 用于包含一个自己编写的 mapper。 -1. `restart.include` 热部署 -1. `logging.level.net.codingme.boot.domain.mapper=debug` 输出 Mybatis SQL 这里要指定自己 mapper 所在的包路径 - -## 通用 Mapper -在上一篇文章中也演示了自动生成,那是通用的一种生成方式,这次我们引入通用 Mapper 再进行生成,这样生成的代码更加简洁。 - -为了方便理解,先看一下项目最终结构。 - -![项目接口](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/58fef28ddbcf12f769f2424db21d7fa3.png) - -### 自动生成配置 -自动生成通用接口分为两步,第一步是编写生成配置文件,注释已经添加了,直接看代码。 -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -``` -与上次不同的是这次增加了两个 `plugin`,后面生成的 Mapper 接口都会自动继承这些类。 -### 自动生成代码 -写好配置文件之后,还需要写一个生成程序,用于加载配置文件,运行就可以生成相关的实体类、Mapper 接口、Mapper xml . -```java -import org.mybatis.generator.api.MyBatisGenerator; -import org.mybatis.generator.config.Configuration; -import org.mybatis.generator.config.xml.ConfigurationParser; -import org.mybatis.generator.internal.DefaultShellCallback; -import java.io.File; -import java.util.ArrayList; -/** - *

- * Mybatis generator的逆向生成工具类 - * - * @Author niujinpeng - */ -public class MybatisGenerator { - - public void generator() throws Exception { - ArrayList warnings = new ArrayList<>(); - boolean overwrite = true; - // 指定你想工程配置文件 - File configFile = new File("generatorConfig.xml"); - System.out.println(configFile.getAbsolutePath()); - ConfigurationParser cp = new ConfigurationParser(warnings); - Configuration config = cp.parseConfiguration(configFile); - DefaultShellCallback callback = new DefaultShellCallback(overwrite); - MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config, callback, warnings); - myBatisGenerator.generate(null); - for (String warning : warnings) { - System.out.println(warning); - } - } - - public static void main(String[] args) throws Exception { - MybatisGenerator mybatisGenerator = new MybatisGenerator(); - mybatisGenerator.generator(); - } -} -``` -### 自动生成结果 -运行完毕上面程序之后,自动生成了 Book.java. -```java -@Table(name = "book") -@ToString // 手动添加的 tostring 注解 -public class Book { - @Id - private Integer id; - - /** - * 书籍作者 - */ - private String author; - - /** - * 书籍名称 - */ - private String name; - - /** - * 书籍价格 - */ - private Float price; - - // 省略下面的自动生成代码 -``` -### 通用 Mapper -上面的程序也自动生成了 BookMapper 接口,且继承了配置的 MySqlMapper 和 Mapper 接口。 -```java -import net.codingme.boot.domain.Book; -import org.springframework.stereotype.Repository; -import tk.mybatis.mapper.common.Mapper; -import tk.mybatis.mapper.common.MySqlMapper; - -@Repository -public interface BookMapper extends MySqlMapper, Mapper { -} -``` -这两个接口里实现了很多常用操作。 - -![通用 Mapper 里的方法](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/51f20a7f33ad27d9495046c236dec145.png) - -生成完成之后要在 Springboot 启动器上添加 MapperScan 注解指定要扫描的 mapper 位置。 -```java -@tk.mybatis.spring.annotation.MapperScan(basePackages = "net.codingme.boot.domain.mapper") -@SpringBootApplication -public class BootApplication { - - public static void main(String[] args) { - SpringApplication.run(BootApplication.class, args); - } -} -``` - -## 单元测试和分页测试 -编写 BookMapperTest 单元测试用于测试 BookMapper 的方法。 - -```java -package net.codingme.boot.domain.mapper; - -import com.github.pagehelper.Page; -import com.github.pagehelper.PageHelper; -import com.github.pagehelper.PageInfo; -import net.codingme.boot.domain.Book; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -import java.util.List; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class BookMapperTest { - - @Autowired - private BookMapper bookMapper; - - @Test - public void testSelectOne() { - Book book = new Book(); - book.setId(2); - Book selectOne = bookMapper.selectOne(book); - Assert.assertNotNull(selectOne); - System.out.println(selectOne); - } - - @Test - public void testSelectByPrimaryKey() { - Book book = bookMapper.selectByPrimaryKey(2); - Assert.assertNotNull(book); - System.out.println(book); - } - - /** - * 分页测试 - */ - @Test - public void testSelectPageInfo() { - PageHelper.startPage(2, 3); - List bookList = bookMapper.selectAll(); - Assert.assertNotNull(bookList); - System.out.println("查询出数量:" + bookList.size()); - PageInfo pageInfo = PageInfo.of(bookList); - System.out.println("总数量:" + pageInfo.getTotal()); - System.out.println("总页数:" + pageInfo.getPages()); - System.out.println("页大小:" + pageInfo.getPageSize()); - System.out.println("第几页:" + pageInfo.getPageNum()); - System.out.println("当前量:" + pageInfo.getSize()); - } - - /** - * 分页测试 - */ - @Test - public void testSelectPage() { - PageHelper.startPage(2, 3); - List bookList = bookMapper.selectAll(); - Assert.assertNotNull(bookList); - System.out.println("查询出数量:" + bookList.size()); - System.out.println("总数量:" + ((Page)bookList).getTotal()); - System.out.println("总页数:" + ((Page)bookList).getPages()); - System.out.println("第几页:" + ((Page)bookList).getPageNum()); - } - -} -``` -从代码中可以看到分页的实现主要是 PageHelper 的设置,在设置 PageHelper 之后的第一个查询会进行分页。像上面的例子会查询第二页,每页三条这样。 -```java -PageHelper.startPage(2, 3); -List bookList = bookMapper.selectAll(); -``` -其实使用了分页插件之后返回的数据类型是一个 Page 类,总数等分页信息都已经返回,如果要取出来使用就需要强制转换类型然后取出,上面也是演示了两种方式。 -```java -// 方式 1 -PageInfo pageInfo = PageInfo.of(bookList); -System.out.println("总数量:" + pageInfo.getTotal()); -System.out.println("总页数:" + pageInfo.getPages()); -System.out.println("页大小:" + pageInfo.getPageSize()); -System.out.println("第几页:" + pageInfo.getPageNum()); -System.out.println("当前量:" + pageInfo.getSize()); - -// 方式 2 -System.out.println("查询出数量:" + bookList.size()); -System.out.println("总数量:" + ((Page)bookList).getTotal()); -System.out.println("总页数:" + ((Page)bookList).getPages()); -System.out.println("第几页:" + ((Page)bookList).getPageNum()); -``` -运行 BookMapperTest 类测试所有的单元测试。 - -![单元测试结果](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/0dd65eb033d1b3e872cd2ed41f201dca.png) - -发现单元测试全部通过,查看一个分页查询(testSelectPageInfo)输出情况。 -```log -2019-03-08 16:07:52.226 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectAll_COUNT : ==> Preparing: SELECT count(0) FROM book -2019-03-08 16:07:52.227 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectAll_COUNT : ==> Parameters: -2019-03-08 16:07:52.229 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectAll_COUNT : <== Total: 1 -2019-03-08 16:07:52.231 DEBUG 26764 --- [ main] n.c.b.d.mapper.BookMapper.selectAll : ==> Preparing: SELECT id,author,name,price,create_time,description FROM book LIMIT ?, ? -2019-03-08 16:07:52.233 DEBUG 26764 --- [ main] n.c.b.d.mapper.BookMapper.selectAll : ==> Parameters: 3(Integer), 3(Integer) -2019-03-08 16:07:52.236 DEBUG 26764 --- [ main] n.c.b.d.mapper.BookMapper.selectAll : <== Total: 3 -查询出数量:3 -总数量:12 -总页数:4 -页大小:3 -第几页:2 -当前量:3 -``` -再查看一个普通查询(testSelectByPrimaryKey)输出情况。 -```log -2019-03-08 16:07:52.241 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectByPrimaryKey : ==> Preparing: SELECT id,author,name,price,create_time,description FROM book WHERE id = ? -2019-03-08 16:07:52.242 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectByPrimaryKey : ==> Parameters: 2(Integer) -2019-03-08 16:07:52.244 DEBUG 26764 --- [ main] n.c.b.d.m.BookMapper.selectByPrimaryKey : <== Total: 1 -Book(id=2, author=金庸, name=笑傲江湖, price=12.0, createTime=Sat Sep 01 10:10:12 GMT+08:00 2018, description=是作家金庸创作的一部长篇武侠小说) -``` - -文中代码已经上传到 Github [Spring Boot 连接数据库 - Mybatis 插件](https://github.com/niumoo/springboot) - -想要了解这几个插件的其他信息,可以查看官方文档。 - -1. [如何使用分页插件](https://pagehelper.github.io/docs/howtouse/) -1. [Mapper插件](https://github.com/abel533/Mapper/wiki/1.3-spring-boot) -1. [ MyBatis Generator](http://www.mybatis.org/generator/) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-13-email.md b/docs/springboot/springboot-13-email.md deleted file mode 100644 index 27b3fa6..0000000 --- a/docs/springboot/springboot-13-email.md +++ /dev/null @@ -1,372 +0,0 @@ ---- -title: Springboot 系列(十三)使用邮件服务 -toc_number: true -date: 2019-03-12 00:20:22 -url: springboot/springboot-13-email -tags: - - Springboot - - 邮件 - - E-mail -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![桌面生活(来自网络)](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/00a2b4768bf601da67118c0acb347876.jpg)我们这个时代,邮件服务不管是对于工作上的交流,还是平时的各种邮件通知,都是一个十分重要的存在。Java 从很早时候就可以通过 Java mail 支持邮件服务。Spring 更是对 Java mail 进行了进一步的封装,抽象出了 `JavaMailSender`. 后来随着 Springboot 的出现,理所当然的出现了 `spring-boot-starter-mail`. 不管怎么说,每次的封装都让使用变得越来越简单。 - -## Springboot mail 依赖 - -创建 Springboot 项目不提,先看一下总体目录结构。 -![项目结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/87989c22d455e3d428811562649c840d.jpg) - -直接引入 Springboot 邮件服务所需的依赖。 -```xml - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-mail - - - - org.springframework.boot - spring-boot-starter-thymeleaf - - - - org.projectlombok - lombok - true - - - - org.springframework.boot - spring-boot-starter-test - test - - -``` - -## Springboot mail 配置 -使用邮件服务需要配置自己可以使用的邮箱信息,一般需要配置发送协议 SMTP、邮箱帐号(本次以126邮箱为例)、邮箱密码以及编码格式。 -```properties -spring.mail.host=smtp.126.com -spring.mail.port=25 -# 你的邮箱地址 -spring.mail.username=niumoo@126.com -# 你的授权码(126 和 163 以及 qq 邮箱 都需要授权码登录,没有授权码的直接登录网页版邮箱设置里设置) -spring.mail.password=password -spring.mail.default-encoding=UTF-8 -``` -## Springboot mail 文本邮件 -文本邮件是最简单也是最基础的一种邮件,使用 Spring 封装的 `JavaMailSender` 直接发送就可以了。 - -创建 `MailService` 类,注入 `JavaMailSender` 用于发送邮件,使用 `@Value("${spring.mail.username}")` 绑定配置文件中的参数用于设置邮件发送的来邮箱。使用 `@Service` 注解把 `MailService` 注入到 Spring 容器,使用 `Lombok` 的 `@Slf4j` 引入日志。 -```java -/** - *

- * 邮件服务 - * - * @Author niujinpeng - * @Date 2019/3/10 21:45 - */ -@Service -@Slf4j -public class MailService { - - @Value("${spring.mail.username}") - private String from; - - @Autowired - private JavaMailSender mailSender; - - /** - * 发送简单文本邮件 - * - * @param to - * @param subject - * @param content - */ - public void sendSimpleTextMail(String to, String subject, String content) { - SimpleMailMessage message = new SimpleMailMessage(); - message.setTo(to); - message.setSubject(subject); - message.setText(content); - message.setFrom(from); - mailSender.send(message); - log.info("【文本邮件】成功发送!to={}", to); - } -} -``` -创建 Springboot 的单元测试类测试文本邮件,实验中的收信人为了方便,都设置成了自己的邮箱。 -```java -@RunWith(SpringRunner.class) -@SpringBootTest -public class MailServiceTest { - - @Autowired - private MailService mailService; - @Autowired - private TemplateEngine templateEngine; - - @Test - public void sendSimpleTextMailTest() { - String to = "niumoo@126.com"; - String subject = "Springboot 发送简单文本邮件"; - String content = "

第一封 Springboot 简单文本邮件

"; - mailService.sendSimpleTextMail(to, subject, content); - } -} -``` -运行单元测试,测试文本邮件的发送。 - -PS:如果运行报出异常 `AuthenticationFailedException: 535 Error`. 一般都是用户名和密码有误。 -```log -Caused by: javax.mail.AuthenticationFailedException: 535 Error: authentication failed - - at com.sun.mail.smtp.SMTPTransport$Authenticator.authenticate(SMTPTransport.java:965) - at com.sun.mail.smtp.SMTPTransport.authenticate(SMTPTransport.java:876) - at com.sun.mail.smtp.SMTPTransport.protocolConnect(SMTPTransport.java:780) - at javax.mail.Service.connect(Service.java:366) - at org.springframework.mail.javamail.JavaMailSenderImpl.connectTransport(JavaMailSenderImpl.java:517) - at org.springframework.mail.javamail.JavaMailSenderImpl.doSend(JavaMailSenderImpl.java:436) - ... 34 more -``` -正常运行输出成功发送的日志。 -```log -2019-03-11 23:35:14.743 INFO 13608 --- [ main] n.codingme.boot.service.MailServiceTest : Started MailServiceTest in 3.964 seconds (JVM running for 5.749) -2019-03-11 23:35:24.718 INFO 13608 --- [ main] net.codingme.boot.service.MailService : 【文本邮件】成功发送!to=niumoo@126.com -``` -查看邮箱中的收信。 - -![文本邮件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/e391dbce4b614779ae9a024908ef0275.jpg) - -文本邮件正常收到,同时可见文本邮件中的 HTML 标签也不会被解析。 - -## Springboot mail HTML 邮件 -在上面的 `MailService` 类里新加一个方法 `sendHtmlMail`,用于测试 HTML 邮件。 -```java - /** - * 发送 HTML 邮件 - * - * @param to - * @param subject - * @param content - * @throws MessagingException - */ - public void sendHtmlMail(String to, String subject, String content) throws MessagingException { - MimeMessage message = mailSender.createMimeMessage(); - MimeMessageHelper messageHelper = new MimeMessageHelper(message, true); - messageHelper.setFrom(from); - messageHelper.setTo(to); - messageHelper.setSubject(subject); - // true 为 HTML 邮件 - messageHelper.setText(content, true); - mailSender.send(message); - log.info("【HTML 邮件】成功发送!to={}", to); - } -``` -在测试方法中增加 HTML 邮件测试方法。 -```java - @Test - public void sendHtmlMailTest() throws MessagingException { - String to = "niumoo@126.com"; - String subject = "Springboot 发送 HTML 邮件"; - String content = "

Hi~

第一封 Springboot HTML 邮件

"; - mailService.sendHtmlMail(to, subject, content); - } -``` -运行单元测试,查看收信情况。 - -![HTML 邮件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/147b7f68d12e0080b408a581414fb92a.jpg) - -HTML 邮件正常收到,HTML 标签也被解析成对应的样式。 - -## Springboot mail 附件邮件 -在上面的 `MailService` 类里新加一个方法 `sendAttachmentMail`,用于测试 附件邮件。 -```java - /** - * 发送带附件的邮件 - * - * @param to - * @param subject - * @param content - * @param fileArr - */ - public void sendAttachmentMail(String to, String subject, String content, String... fileArr) - throws MessagingException { - MimeMessage mimeMessage = mailSender.createMimeMessage(); - MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true); - messageHelper.setFrom(from); - messageHelper.setTo(to); - messageHelper.setSubject(subject); - messageHelper.setText(content, true); - - // 添加附件 - for (String filePath : fileArr) { - FileSystemResource fileResource = new FileSystemResource(new File(filePath)); - if (fileResource.exists()) { - String filename = fileResource.getFilename(); - messageHelper.addAttachment(filename, fileResource); - } - } - mailSender.send(mimeMessage); - log.info("【附件邮件】成功发送!to={}", to); - } -``` -在测试方法中增加附件邮件测试方法。 -```java - @Test - public void sendAttachmentTest() throws MessagingException { - String to = "niumoo@126.com"; - String subject = "Springboot 发送 HTML 附件邮件"; - String content = "

Hi~

第一封 Springboot HTML 附件邮件

"; - String filePath = "pom.xml"; - mailService.sendAttachmentMail(to, subject, content, filePath, filePath); - } -``` -运行单元测试,查看收信情况。 - -![附件邮件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/0ab42e65ae35c7970d7954bf5a985a79.jpg) - -带附件的邮件正常收到,多个附件的实现方式同理。 - -## Springboot mail 图片邮件 -图片邮件和其他的邮件方式略有不同,图片邮件需要先在内容中定义好图片的位置并出给一个记录 ID ,然后在把图片加到邮件中的对于的 ID 位置。 - -在上面的 `MailService` 类里新加一个方法 `sendImgMail`,用于测试 附件邮件。 -```java - /** - * 发送带图片的邮件 - * - * @param to - * @param subject - * @param content - * @param imgMap - */ - public void sendImgMail(String to, String subject, String content, Map imgMap) - throws MessagingException { - MimeMessage mimeMessage = mailSender.createMimeMessage(); - MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true); - messageHelper.setFrom(from); - messageHelper.setTo(to); - messageHelper.setSubject(subject); - messageHelper.setText(content, true); - // 添加图片 - for (Map.Entry entry : imgMap.entrySet()) { - FileSystemResource fileResource = new FileSystemResource(new File(entry.getValue())); - if (fileResource.exists()) { - String filename = fileResource.getFilename(); - messageHelper.addInline(entry.getKey(), fileResource); - } - } - mailSender.send(mimeMessage); - log.info("【图片邮件】成功发送!to={}", to); - } -``` -在测试方法中增加图片邮件测试方法,测试方法中使用的 apple.png 是项目里的一个图片。可以看上面的项目结构。 -```java - @Test - public void sendImgTest() throws MessagingException { - String to = "niumoo@126.com"; - String subject = "Springboot 发送 HTML 图片邮件"; - String content = - "

Hi~

第一封 Springboot HTML 图片邮件


"; - String imgPath = "apple.png"; - Map imgMap = new HashMap<>(); - imgMap.put("img01", imgPath); - imgMap.put("img02", imgPath); - mailService.sendImgMail(to, subject, content, imgMap); - } -``` -运行单元测试,查看收信情况。 - -![图片邮件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/a676fadf6b40195c7de746adff0700bc.jpg) - -两个图片正常显示在邮件里。 - -## Springboot mail 模版邮件 -模版邮件的用处很广泛,像经常收到的注册成功邮件或者是操作通知邮件等都是模版邮件,模版邮件往往只需要更改其中的几个变量。Springboot 中的模版邮件首选需要选择一款模版引擎,在引入依赖的时候已经增加了模版引擎 `Thymeleaf`. - -模版邮件首先需要一个邮件模版,我们在 `Templates` 下新建一个 `HTML` 文件 `RegisterSuccess.html`. 其中的 username 是给我们自定义的。 -```html - - - - - 注册成功通知 - - -

[[${username}]],您好! -

-

- 新的公钥已添加到你的账户:
- 标题: HP-WIN10
- 如果公钥无法使用,你可以在这里重新添加: SSH Keys -

- - -``` -在邮件服务 `MailService` 中注入模版引擎,然后编写邮件模版发送代码。 -```java - @Autowired - private TemplateEngine templateEngine; - - /** - * 发送模版邮件 - * - * @param to - * @param subject - * @param paramMap - * @param template - * @throws MessagingException - */ - public void sendTemplateMail(String to, String subject, Map paramMap, String template) - throws MessagingException { - Context context = new Context(); - // 设置变量的值 - context.setVariables(paramMap); - String emailContent = templateEngine.process(template, context); - sendHtmlMail(to, subject, emailContent); - log.info("【模版邮件】成功发送!paramsMap={},template={}", paramMap, template); - } -``` -在单元单元测试中增加模版邮件测试方法,然后发送邮件测试。 -```java - @Test - public void sendTemplateMailTest() throws MessagingException { - String to = "niumoo@126.com"; - String subject = "Springboot 发送 模版邮件"; - Map paramMap = new HashMap(); - paramMap.put("username", "Darcy"); - mailService.sendTemplateMail(to, subject, paramMap, "RegisterSuccess"); - } -``` -查看收信情况。 - -![模版邮件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/8244d50efe797cfa3ceb14aab7b7b461.jpg) - -可以发现模版邮件已经正常发送了。 - -## Springboot mail 补充 -上面的例子中,是 Springboot 邮件服务的基本用法,代码也有很多重复,和实际的使用情况相比还有很多不足,比如缺少`异常处理机制`,在发送失败时的`重试机制`也没有,实际情况中邮件服务往往对实时性不高,多说情况下会用于`异步请求`。 - -文章相关代码已经上传 Github [Spring Boot 相关整合 - 邮件服务](https://github.com/niumoo/springboot/tree/master/springboot-mail)。 - -<完> -本文原发于个人博客:[https://www.wdbyte.com](https://www.wdbyte.com) 转载请注明出处。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-14-https.md b/docs/springboot/springboot-14-https.md deleted file mode 100644 index f685a4a..0000000 --- a/docs/springboot/springboot-14-https.md +++ /dev/null @@ -1,237 +0,0 @@ ---- -title: Springboot 系列(十四)迅速启用 HTTPS 加密你的网站 -toc_number: false -date: 2019-08-07 00:10:22 -url: springboot/springboot-14-https -tags: - - Springboot - - Https -categories: - - Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![HTTPS](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572308662878.png) - -## 1. 获取 HTTPS 证书 - -正常情况下 HTTPS 证书需要从证书授权中心获得,这样获得的证书才具有公信力,也会被各种浏览器客户端所认可。常见的证书品牌如 Symantec,GeoTrustm,TrustAsia,Symantec 等。不过在 Springboot 的 HTTPS 实验中就没有必要去申请了,我们可以使用 Java 自带的 **keytool** 生成 HTTPS 证书。 - - - -查看 keytool 工具使用说明。 - -```shell -D:\>keytool -密钥和证书管理工具 -命令: - -certreq 生成证书请求 - -changealias 更改条目的别名 - -delete 删除条目 - -exportcert 导出证书 - -genkeypair 生成密钥对 - -genseckey 生成密钥 - -gencert 根据证书请求生成证书 - -importcert 导入证书或证书链 - -importpass 导入口令 - -importkeystore 从其他密钥库导入一个或所有条目 - -keypasswd 更改条目的密钥口令 - -list 列出密钥库中的条目 - -printcert 打印证书内容 - -printcertreq 打印证书请求的内容 - -printcrl 打印 CRL 文件的内容 - -storepasswd 更改密钥库的存储口令 - -使用 "keytool -command_name -help" 获取 command_name 的用法 - -D:\>keytool -genkeypair --help -keytool -genkeypair [OPTION]... -生成密钥对 -选项: - -alias 要处理的条目的别名 - -keyalg 密钥算法名称 - -keysize 密钥位大小 - -sigalg 签名算法名称 - -destalias 目标别名 - -dname 唯一判别名 - -startdate 证书有效期开始日期/时间 - -ext X.509 扩展 - -validity 有效天数 - -keypass 密钥口令 - -keystore 密钥库名称 - -storepass 密钥库口令 - -storetype 密钥库类型 - -providername 提供方名称 - -providerclass 提供方类名 - -providerarg 提供方参数 - -providerpath 提供方类路径 - -v 详细输出 - -protected 通过受保护的机制的口令 -``` - -通过上面的 keytool ,我们生成自己的自签名证书。 - -```shell -D:\>keytool -genkeypair -alias tomcat_https -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore d:/tomcat_https.keystore -storepass 123456 -您的名字与姓氏是什么? - [Unknown]: darcy -您的组织单位名称是什么? - [Unknown]: codingme -您的组织名称是什么? - [Unknown]: codingme -您所在的城市或区域名称是什么? - [Unknown]: ShangHai -您所在的省/市/自治区名称是什么? - [Unknown]: ShangHai -该单位的双字母国家/地区代码是什么? - [Unknown]: ZN -CN=darcy, OU=codingme, O=codingme, L=ShangHai, ST=ShangHai, C=ZN是否正确? - [否]: y -D:\> -``` - -这时候已经在我们指定的位置下生成了证书文件,如果需要查看证书信息,可以使用 keytool 的 list 命令,可以看到密钥库类型是 JKS,在后面的配置里会用到。 - -```shell -D:\>keytool -list -keystore tomcat_https.keystore -输入密钥库口令: - -密钥库类型: JKS -密钥库提供方: SUN - -您的密钥库包含 1 个条目 - -tomcat_https, 2019-4-21, PrivateKeyEntry, -证书指纹 (SHA1): 1E:5F:15:9C:45:BD:D3:2A:7E:7F:1F:83:56:B8:74:E0:8B:CA:FD:F6 - -D:\> -``` - -自己生成的 HTTPS 证书只能用来自己测试,真正用于网络上时,浏览器会显示证书无法信息。因此如果想要得到一个真实有效的证书,请看文章末尾。 - -## 2. 配置 HTTPS 证书 - -创建一个 Springboot 项目这里不提,拷贝上一步骤中生成的 tomcat_https.keystore 证书文件到**src/main/resource** 文件夹下,先看下总体的项目结构。 - -![项目结构如下](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/3f4682d8b6eaa6ac7bf29eae9f9d5109.png) - - -然后在 application.yml 文件中配置 HTTPS 相关信息。直接配置了端口号为 **443**,443是 HTTPS 的默认端口,这样在使用 HTTPS 就行访问的时候就不需要写额外的端口号了。 - -```yml -# 配置 HTTPS 相关信息 -server: - port: 443 - http-port: 80 # 为了后面的配置使用,暂时无用 - ssl: - enabled: true - key-store: classpath:tomcat_https.keystore # 证书文件 - key-password: 123456 # 密码 - key-store-type: JKS # 密钥库类型 - key-alias: tomcat_https -``` - -这时,已经可以通过 HTTPS 进行页面访问了。 - -## 3. 测试 HTTPS 证书 - -直接编写一个 接口用于测试。 - -```java -/** - *

- * Https 接口控制类 - * - * @Author niujinpeng - * @Date 2019/4/20 22:59 - */ -@RestController -public class HttpsController { - - @GetMapping(value = "/hello") - public String hello() { - return "Hello HTTPS"; - } - -} -``` - -启动之后可以通过 [https://localhost/hello](https://localhost/hello) 进行访问了。 - -![HTTPS 访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/29a658a7761aa462cb80e1e34e5b0017.png) - - -当然,由于是自己生成的证书,会提示不安全,继续访问即可,如果是正常申请或者购买的证书就不会有这个问题的。 - -## 4. HTTP 跳转 HTTPS - -在上面的测试里,HTTPS 已经可以访问了,但是 HTTP 却不能访问,大多数情况下在启用了 HTTPS 之后,都会希望 HTTP 的请求会自动跳转到 HTTPS,这个在 Springboot 里自然也是可以实现的。我们只需要写一个配置类把 HTTP 请求直接转发到 HTTPS 即可。 - -```java -/** - *

- * HTTP 强制跳转 HTTPS - * - * @Author niujinpeng - * @Date 2019/4/21 17:47 - */ -@Configuration -public class Http2Https { - - @Value("${server.port}") - private int sslPort; - @Value("${server.http-port}") - private int httpPort; - - @Bean - public TomcatServletWebServerFactory servletContainerFactory() { - TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { - @Override - protected void postProcessContext(Context context) { - SecurityConstraint securityConstraint = new SecurityConstraint(); - securityConstraint.setUserConstraint("CONFIDENTIAL"); - SecurityCollection collection = new SecurityCollection(); - collection.addPattern("/*"); - securityConstraint.addCollection(collection); - context.addConstraint(securityConstraint); - } - }; - Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol"); - connector.setScheme("http"); - connector.setPort(httpPort); - connector.setRedirectPort(sslPort); - tomcat.addAdditionalTomcatConnectors(connector); - return tomcat; - } -} -``` - -再次启动之后,使用 [http://localhost/hello](http://localhost/hello) 访问会自动跳转到 [https://localhost/hello](https://localhost/hello). - - - -## 附录 - -如果需要申请免费证书,可以在腾讯云上免费申请,请参考: - -- [免费版 DV SSL 证书申请](https://cloud.tencent.com/document/product/400/6813#.E8.8E.B7.E5.8F.96.E8.AF.81.E4.B9.A6)。 - -如果想要自己安装证书,请参考: - -- [Apache 服务器证书安装](https://cloud.tencent.com/document/product/400/35243) -- [Nginx 服务器证书安装](https://cloud.tencent.com/document/product/400/35244) -- [Tomcat 服务器证书安装](https://cloud.tencent.com/document/product/400/35224) -- [Windows IIS 服务器证书安装](https://cloud.tencent.com/document/product/400/35225) - -🐟 文章相关代码已经上传 Github [Spring Boot https](https://github.com/niumoo/springboot/tree/master/springboot-web-https), 欢迎⭐Star️,欢迎 Fork ! - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-15-my-starter.md b/docs/springboot/springboot-15-my-starter.md deleted file mode 100644 index 98ee61d..0000000 --- a/docs/springboot/springboot-15-my-starter.md +++ /dev/null @@ -1,413 +0,0 @@ ---- -title: Springboot 系列(十五)如何编写自己的 Springboot starter -toc_number: false -date: 2019-11-01 08:08:08 -url: springboot/springboot-15-my-starter -tags: -- Springboot -- Springboot starter -categories: -- Springboot -typora-root-url: ..\ ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![图片来自网络]](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572539675483.png) - -# 1. 前言 - -`Springboot` 中的自动配置确实方便,减少了我们开发上的复杂性,那么自动配置原理是什么呢?之前我也写过了一篇文章进行了分析。 -[Springboot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/)。 - -由于自动配置用到了配置文件的绑定,如果你还不知道常见的配置文件的用法,可以参考这篇文章。 -[Springboot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/)。 - -在这一次,通过学习 `Springboot` 自动配置模式,编写一个自己的 `starter`,用来加深对自动配置的理解。 - -熟悉模式,有助于提升编写的 `starter` 的规范性,编写自己的 `starter` 之前先来学习 `Springboot` 官方 `starter` 以及常见框架的整合 `starter` 的编写方式 ,可以领略到其中的奥秘。 - -# 2. Springboot 官方模式 - -选择一个官方的自动配置进行分析,这里就选择常见的配置端口号配置。 - -## 2.1. 引入依赖 - -使用端口号之前我们需要先引入 web 依赖。 - -```xml - - org.springframework.boot - spring-boot-starter-web - -``` - -如果你观察 `starter` 多的话,也许你发已经发现了一个**模式**,`Springboot` 官方的 `starter `的名字都是 `spring-boot-starter-xxxx`命名的。 - -查看 `spring-boot-starter-web` 会发现,其实这个依赖只是一个空盒子,除了依赖其他 `pom` 之外,没有一行代码。 - -![spring-boot-starter-web](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572480685107.png) - -这时,发现了另外一个**模式**:`starter` 只依赖其他 `pom`,不做代码实现。 - -那么 `spring-boot-starter-web` 到底依赖了哪些内容? - -![spring-boot-starter-web 的依赖](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572481136481.png) - -观察这个依赖信息,然后再参照其他的官方 `starter` ,可以找到几个固定的引入,可以被称之为**模式**的依赖引入。 - -1. 依赖 `spring-boot-starter`。 -2. 依赖 `spring-boot-autoconfigure`。 - -## 2.2. 自动配置 - -引入依赖只有配置端口号,像这样。 - -```properties -server.port=8090 -``` - -IDEA 中可以通过点击 `server.port` 找到这个配置绑定的类文件。可以看到配置最终会注入到类`ServerProperties` 类的 `port` 属性上。 - -![Server 属性配置](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572478509712.png) - -那么这个 `ServerProperties` 到底是哪里使用的呢?继续查找,找到一个和 `Servlet` 的有关的调用。 - -![getPort 的调用](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572478794011.png) - -发现是被 `ServletWebServerFactoryCustomizer`类进行了调用,这个类里面定义了 - -```java -private final ServerProperties serverProperties; -``` - -用来使用配置的属性。 -继续查看这个类的调用,发现只有一个类使用这个类,这个类是`ServletWebServerFactoryAutoConfiguration`。 - -![ServletWebServerFactoryAutoConfiguration 类](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572479738431.png) - -根据我们对注解的理解,这个类就是自动配置主要类了。同时自动配置类都是以 `AutoConfiguration` 结尾。 - -看这个类的几个注解的意思。 - -1. 优先级别较高。 - -```java -@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) -``` - -2. 只有在 `ServletRequest` 类存在和是 Web 应用时生效。 - -```java -@ConditionalOnClass(ServletRequest.class) -@ConditionalOnWebApplication(type = Type.SERVLET) -``` - -3. 开启了 `ServerProperties` 的配置绑定。 - -```java -@EnableConfigurationProperties(ServerProperties.class) -``` - -4. 导入了几个类。 - -```java -@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class, - ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, - ServletWebServerFactoryConfiguration.EmbeddedJetty.class, - ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) -``` - -同时注入配置到 Bean 工厂以供其他地方调用。 - -```java -@Bean -public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) { - return new ServletWebServerFactoryCustomizer(serverProperties); -} -``` - -自动配置仅仅是这些东西吗?根据之前文章里的分析,我们知道不止代码,至少还有一个指定自动配置类的配置文件需要读取。也就是 `spring.factories` 文件。 - -![spring.factories](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572480162756.png) - -如果你不知道,可以先看这篇文章。[Springboot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/) 。 -事实确实如此,可以在 `spring.factories` 中找到上面跟踪到的类。 -也就是 `ServletWebServerFactoryAutoConfiguration`. - -根据上面的分析,可以发现 `Springboot` 官方 `starter` 的几个**模式**。 -1. 使用 `XXXProperties` 自动绑定 `XXX` 开头的配置信息,如:`ServerProperties`。 -2. 把 `XXXProperties` 定义到要使用的类中,如:`ServletWebServerFactoryCustomizer`。 -3. 编写一个 `XXXAutoConfiguration` ,开启 `XXXProperties` 的自动配置,限定生效场景,创建需要的类到 `Bean` 工厂。如:`ServletWebServerFactoryAutoConfiguration`。 - -# 3. 第三方集成模式 - -`Springboot` 官方如果把所有的框架都编写成 `starter`,是不现实的。因此很多第三方框架需要主动集成到 `springboot`,所以我们选择一个常用的框架分析它的 `starter` 实现。因为已经看过了 `springboot` 官方 `starter` 是如何配置的, 第三方框架也是类似,所以在下面观察的过程中会直接指出相同点,而不再做对比详细对比。 - -这里选择 `mybatis-spring-boot-starter` 进行学习分析。 - -## 3.1 引入依赖 - -```xml - - org.mybatis.spring.boot - mybatis-spring-boot-starter - 1.3.2 - -``` - -这里 `mybatis` 框架的 `starter` 依赖符合一定的**规则**,即 **xxx-spring-boot-starter**. - -观察这个 `starter`,发现它也没有做任何的代码实现,这一点和 `springboot` 官方一致。 - -![mybatis-spring-boot-starter](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572532932799.png) - -查看 `mybatis-spring-boot-starter` 的依赖项,有很多,其中和自动配置有关的主要是。 - -```xml - - org.mybatis.spring.boot - mybatis-spring-boot-autoconfigure - -``` - -## 3.2 自动配置 - -查看 `mybatis-spring-boot-autoconfigure` 的内容发现和 `springboot` 官方的 `autoconfigure`结构上是差不多的。 - -![mybatis-spring-boot-autoconfigure](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572533517315.png) - -`mybatis` 的自动配置也是通过 `spring.factories` 来指明自动配置,然后通过 `XxxAutoConfiguration` 绑定 `XxxProperties` 来进行自动配置. - -![MybatisAutoConfiguration](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572533809395.png) - -在原理上,和上面 `springboot` 官方的 `starter`是相同的,所以不做过多的介绍了。 - -# 4. 编写自己的 starter - -说了那么多,终于到了实操环节,通过上面的介绍,我们可以大致得出编写自己的 `starter `步骤。 - -1. 创建名字为 `xxx-spring-boot-starter` 的启动器项目。 -2. 创建名字为 `xxx-spring-boot-autoconfigure`的项目。 - - 编写属性绑定类 `xxxProperties`. - - 编写服务类,引入 `xxxProperties`. - - 编写自动配置类`XXXAutoConfiguration`注入配置。 - - 创建 `spring.factories` 文件,用于指定要自动配置的类。 -3. 启动器项目为空项目,用来引入 `xxx-spring-boot-autoconfigure`等其他依赖。 -4. 项目引入 `starter`,配置需要配置的信息。 - -## 4.1 创建启动器项目 - -由于启动器不需要代码实现,只需要依赖其他项目,所以直接创建一个空的 maven 项目。但是名字要规范。 -这里创建的 `starter` 是 `myapp-spring-boot-starter`。 - -![myapp-spring-boot-starter](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572534904150.png) - -pom 文件非常简单,只需要引入接下来要创建的 `myapp-spring-boot-autoconfigure`. - -```xml - - - 4.0.0 - - net.codingme.starter - myapp-spring-boot-starter - 1.0-SNAPSHOT - - - - - net.codingme.starter - myapp-spring-boot-autoconfigure - 0.0.1-SNAPSHOT - - - -``` - -## 4.2 创建自动配置项目 - -结合上面对 `starter` 的分析,直接创建一个名字为 `myapp-spring-boot-autoconfigure` 的项目。项目中只引入 `springboot` 父项目以及 `spring-boot-starter`。 - -```xml - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.2.0.RELEASE - - - net.codingme.starter - myapp-spring-boot-autoconfigure - 0.0.1-SNAPSHOT - myapp-spring-boot-autoconfigure - Demo project for Spring Boot - - 1.8 - - - - - org.springframework.boot - spring-boot-starter - - - - -``` - -项目的总体结构看图。 - -![myapp-spring-boot-starter-autoconfigure](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572536336072.png) - -在 `HelloProperties`中通过注解 `@ConfigurationProperties(prefix = "myapp.hello")`让类中的属性与 `myapp.hello`开头的配置进行绑定。 - -```java -/** - *

- * - * @Author niujinpeng - * @Date 2019/10/29 23:51 - */ -@ConfigurationProperties(prefix = "myapp.hello") -public class HelloProperties { - - private String suffix; - - public String getSuffix() { - return suffix; - } - - public void setSuffix(String suffix) { - this.suffix = suffix; - } -} -``` - -然后在 `HelloService`中的 `sayHello`方法使用 `HelloProperties` 中自动绑定的值。 - -```java -public class HelloService { - HelloProperties helloProperties; - - public String sayHello(String name) { - return "Hello " + name + "," + helloProperties.getSuffix(); - } - - public HelloProperties getHelloProperties() { - return helloProperties; - } - - public void setHelloProperties(HelloProperties helloProperties) { - this.helloProperties = helloProperties; - } -} -``` - -为了让 `HelloService` 可以自动注入且能正常使用 `HelloProperties`,所以我们在 -`HelloServiceAutoConfiguration` 类中把 `HelloProperties.class` 引入,然后把 `HelloService` 注入到 `Bean`。 - -```java -/** - * web应用才生效 - */ -@ConditionalOnWebApplication -/** - * 让属性文件生效 - */ -@EnableConfigurationProperties(HelloProperties.class) -/*** - * 声明是一个配置类 - */ -@Configuration -public class HelloServiceAutoConfiguration { - - @Autowired - private HelloProperties helloProperties; - - @Bean - public HelloService helloService() { - HelloService helloService = new HelloService(); - helloService.setHelloProperties(helloProperties); - return helloService; - } -} -``` - -最后在 `spring.factories`中只需要指定要自动配置的类即可。 - -``` -# Auto Configure -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ -net.codingme.starter.HelloServiceAutoConfiguration -``` - -到这里,自动配置项目就完成了。可以在 `myapp-spring-boot-autoconfigure`项目执行 `mvn install` 把自动配置项目打包到本地仓库,然后使用相同的命令把 `myapp-spring-boot-starter` 安装到仓库。因为后者依赖于前者项目,所以这里前者需要先进 `mvn install`。 - -## 4.3 使用自定义的启动器 - -创建一个 `springboot`项目`myapp-spring-boot-starter-test`。 - -![myapp-spring-boot-starter-test](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572537470601.png) - -引入 `web` 依赖,引入自己编写的 `myapp-spring-boot-starter`. - -```xml - - org.springframework.boot - spring-boot-starter-web - - - - - net.codingme.starter - myapp-spring-boot-starter - 1.0-SNAPSHOT - -``` - -编写一个 `HelloController` 注入自动配置里的 `HelloService`用于测试。 - -```java -@RestController -public class HelloController { - - @Autowired - HelloService helloService; - - @GetMapping("/hello") - public String sayHello(String name) { - return helloService.sayHello(name); - } -} -``` - -由于 `autoConfigure` 项目中定义了 `sayHello` 方法会输出“Hello”+传入的 name + 配置的 `hello.suffix`,所以我们在 `springboot` 配置文件中配置这个属性。 - -```properties -myapp.hello.suffix=早上好 -``` - -运行测试项目,访问 /hello 路径传入一个 name 看看自动配置有没有生效。 - -![访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1572537886411.png) - -从测试结果可以看到自动配置的早上好已经生效了。到这里自己编写的 `starter`也已经完工。 - -项目已经传到 [Github](https://github.com/niumoo/springboot/tree/master/springboot-starter). - https://github.com/niumoo/springboot/tree/master/springboot-starter - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-16-web-swagger.md b/docs/springboot/springboot-16-web-swagger.md deleted file mode 100644 index 950171c..0000000 --- a/docs/springboot/springboot-16-web-swagger.md +++ /dev/null @@ -1,451 +0,0 @@ ---- -title: Springboot 系列(十六)你真的了解 Swagger 文档吗? -toc_number: false -date: 2019-11-26 08:08:08 -url: springboot/springboot-16-web-swagger -tags: -- Springboot -- Swagger -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 前言 - -目前来说,在 Java 领域使用 `Springboot` 构建微服务是比较流行的,在构建微服务时,我们大多数会选择暴漏一个 `REST API` 以供调用。又或者公司采用前后端分离的开发模式,让前端和后端的工作由完全不同的工程师进行开发完成。不管是微服务还是这种前后端分离开发,维持一份完整的及时更新的 `REST API` 文档,会极大的提高我们的工作效率。而传统的文档更新方式(如手动编写),很难保证文档的及时性,经常会年久失修,失去应有的意义。因此选择一种新的 API 文档维护方式很有必要,这也是这篇文章要介绍的内容。 - - -## 1. OpenAPI 规范介绍 - -![Open API](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/oas_original-01.png) - -`OpenAPI Specification` 简称 OAS,中文也称 `OpenAPI` 描述规范,使用 `OpenAPI` 文件可以描述整个 API,它制定了一套的适合通用的与语言无关的 `REST API` 描述规范,如 API 路径规范、请求方法规范、请求参数规范、返回格式规范等各种相关信息,使人类和计算机都可以不需要访问源代码就可以理解和使用服务的功能。 - -下面是 `OpenAPI` 规范中建议的 API 设计规范,基本路径设计规范。 -```shell -https://api.example.com/v1/users?role=admin&status=active -\________________________/\____/ \______________________/ - server URL endpoint query parameters - path -``` -对于传参的设计也有规范,可以像下面这样: - -- [路径参数](https://swagger.io/docs/specification/describing-parameters/#path-parameters), 例如 `/users/{id}` -- [查询参数](https://swagger.io/docs/specification/describing-parameters/#query-parameters), 例如 `/users?role=未读代码` -- [header 参数](https://swagger.io/docs/specification/describing-parameters/#header-parameters), 例如 `X-MyHeader: Value` -- [cookie 参数](https://swagger.io/docs/specification/describing-parameters/#cookie-parameters), 例如 `Cookie: debug=0; csrftoken=BUSe35dohU3O1MZvDCU` - -`OpenAPI` 规范的东西远远不止这些,目前 `OpenAPI` 规范最新版本是 3.0.2,如果你想了解更多的 `OpenAPI` 规范,可以访问下面的链接。 -[OpenAPI Specification (https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md)](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md) - -## 2. Swagger 介绍 -![swagger](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191125225207292.png) - -很多人都以为 `Swagger` 只是一个接口文档生成框架,其实并不是。 Swagger 是一个围绕着 `OpenAPI Specification`(OAS,中文也称 OpenAPI规范)构建的一组开源工具。可以帮助你从 API 的设计到 API 文档的输出再到 API 的测试,直至最后的 API 部署等整个 API 的开发周期提供相应的解决方案,是一个庞大的项目。 Swagger 不仅免费,而且开源,不管你是企业用户还是个人玩家,都可以使用 Swagger 提供的方案构建令人惊艳的 `REST API`。 - -Swagger 有几个主要的产品。 - -- [Swagger Editor](http://editor.swagger.io/?_ga=2.112541447.2078165713.1574600445-3923049.1574128700) – 一个基于浏览器的 Open API 规范编辑器。 -- [Swagger UI](https://swagger.io/swagger-ui/) – 一个将 OpenAPI 规范呈现为可交互在线文档的工具。 -- [Swagger Codegen](https://github.com/swagger-api/swagger-codegen) – 一个根据 OpenAPI 生成调用代码的工具。 - -如果你想了解更多信息,可以访问 Swagger 官方网站 [https://swagger.io](https://swagger.io)。 - - -## 3. Springfox 介绍 - -源于 Java 中 Spring 框架的流行,让一个叫做 Marrty Pitt 的老外有了为 SpringMVC 添加接口描述的想法,因此他创建了一个遵守 OpenAPI 规范(OAS)的项目,取名为 *swagger-springmvc*,这个项目可以让 Spring 项目自动生成 JSON 格式的 OpenAPI 文档。这个框架也仿照了 Spring 项目的开发习惯,使用注解来进行信息配置。 - -后来这个项目发展成为 `Springfox`,再后来扩展出 `springfox-swagger2` ,为了让 JSON 格式的 API 文档更好的呈现,又出现了 `springfox-swagger-ui` 用来展示和测试生成的 OpenAPI 。这里的 springfox-swagger-ui 其实就是上面介绍的 Swagger-ui,只是它被通过 webjar 的方式打包到 jar 包内,并通过 maven 的方式引入进来。 - -上面提到了 Springfox-swagger2 也是通过注解进行信息配置的,那么是怎么使用的呢?下面列举常用的一些注解,这些注解在下面的 Springboot 整合 Swagger 中会用到。 - -| 注解 | 示例 | 描述 | -| ----------------- | ------------------------------------------------------------ | ------------------------------------------ | -| @ApiModel | @ApiModel(value = "用户对象") | 描述一个实体对象 | -| @ApiModelProperty | @ApiModelProperty(value = "用户ID", required = true, example = "1000") | 描述属性信息,执行描述,是否必须,给出示例 | -| @Api | @Api(value = "用户操作 API(v1)", tags = "用户操作接口") | 用在接口类上,为接口类添加描述 | -| @ApiOperation | @ApiOperation(value = "新增用户") | 描述类的一个方法或者说一个接口 | -| @ApiParam | @ApiParam(value = "用户名", required = true) | 描述单个参数 | | | - -更多的 Springfox 介绍,可以访问 Springfox 官方网站。 - -[Springfox Reference Documentation (http://springfox.github.io)](http://springfox.github.io/springfox/docs/current/) - -## 4. Springboot 整合 Swagger - -就目前来说 ,Springboot 框架是非常流行的微服务框架,在微服务框架下,很多时候我们都是直接提供 `REST API` 的。REST API 如果没有文档的话,使用者就很头疼了。不过不用担心,上面说了有一位叫 Marrty Pitt 的老外已经创建了一个发展成为 Springfox 的项目,可以方便的提供 JSON 格式的 OpenAPI 规范和文档支持。且扩展出了 springfox-swagger-ui 用于页面的展示。 - -需要注意的是,这里使用的所谓的 Swagger 其实和真正的 Swagger 并不是一个东西,这里使用的是 Springfox 提供的 Swagger 实现。它们都是基于 OpenAPI 规范进行 API 构建。所以也都可以 Swagger-ui 进行 API 的页面呈现。 - -### 4.1. 创建项目 - -如何创建一个 Springboot 项目这里不提,你可以直接从 [Springboot 官方](https://start.spring.io/) 下载一个标准项目,也可以使用 idea 快速创建一个 Springboot 项目,也可以顺便拷贝一个 Springboot 项目过来测试,总之,方式多种多样,任你选择。 - -下面演示如何在 Springboot 项目中使用 swagger2。 - -### 4.2. 引入依赖 - -这里主要是引入了 springfox-swagger2,可以通过注解生成 JSON 格式的 OpenAPI 接口文档,然后由于 Springfox 需要依赖 jackson,所以引入之。springfox-swagger-ui 可以把生成的 OpenAPI 接口文档显示为页面。Lombok 的引入可以通过注解为实体类生成 get/set 方法。 - -```xml - - - - org.springframework.boot - spring-boot-starter-web - - - spring-boot-starter-json - org.springframework.boot - - - - - - - io.springfox - springfox-swagger2 - 2.9.2 - - - io.springfox - springfox-swagger-ui - 2.9.2 - - - - - com.fasterxml.jackson.core - jackson-databind - 2.5.4 - - - - - org.projectlombok - lombok - true - - -``` - -### 4.3. 配置 Springfox-swagger - -Springfox-swagger 的配置通过一个 Docket 来包装,Docket 里的 apiInfo 方法可以传入关于接口总体的描述信息。而 apis 方法可以指定要扫描的包的具体路径。在类上添加 @Configuration 声明这是一个配置类,最后使用 @EnableSwagger2 开启 Springfox-swagger2。 - -```java -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -/** - *

- * Springfox-swagger2 配置 - * - * @Author niujinpeng - * @Date 2019/11/19 23:17 - */ -@Configuration -@EnableSwagger2 -public class SwaggerConfig { - - @Bean - public Docket createRestApi() { - return new Docket(DocumentationType.SWAGGER_2) - .apiInfo(apiInfo()) - .select() - .apis(RequestHandlerSelectors.basePackage("net.codingme.boot.controller")) - .paths(PathSelectors.any()) - .build(); - } - - private ApiInfo apiInfo() { - return new ApiInfoBuilder() - .title("未读代码 API") - .description("公众号:未读代码(weidudaima) springboot-swagger2 在线借口文档") - .termsOfServiceUrl("https://www.wdbyte.com") - .contact("达西呀") - .version("1.0") - .build(); - } -} -``` - -### 4.4. 代码编写 - -文章不会把所有代码一一列出来,这没有太大意义,所以只贴出主要代码,完整代码会上传到 Github,并在文章底部附上 Github 链接。 - -参数实体类 `User.java`,使用 `@ApiModel` 和 ` @ApiModelProperty` 描述参数对象,使用 ` @NotNull` 进行数据校验,使用 `@Data` 为参数实体类自动生成 get/set 方法。 - -```java -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; - -import javax.validation.constraints.NotNull; -import java.util.Date; - -/** - *

- * 用户实体类 - * - * @Author niujinpeng - * @Date 2018/12/19 17:13 - */ -@Data -@NoArgsConstructor -@AllArgsConstructor -@ApiModel(value = "用户对象") -public class User { - - /** - * 用户ID - * - * @Id 主键 - * @GeneratedValue 自增主键 - */ - @NotNull(message = "用户 ID 不能为空") - @ApiModelProperty(value = "用户ID", required = true, example = "1000") - private Integer id; - - /** - * 用户名 - */ - @NotNull(message = "用户名不能为空") - @ApiModelProperty(value = "用户名", required = true) - private String username; - /** - * 密码 - */ - @NotNull(message = "密码不能为空") - @ApiModelProperty(value = "用户密码", required = true) - private String password; - /** - * 年龄 - */ - @ApiModelProperty(value = "用户年龄", example = "18") - private Integer age; - /** - * 生日 - */ - @DateTimeFormat(pattern = "yyyy-MM-dd hh:mm:ss") - @ApiModelProperty(value = "用户生日") - private Date birthday; - /** - * 技能 - */ - @ApiModelProperty(value = "用户技能") - private String skills; -} -``` - -编写 Controller 层,使用 `@Api` 描述接口类,使用 `@ApiOperation` 描述接口,使用 `@ApiParam` 描述接口参数。代码中在查询用户信息的两个接口上都添加了 ` tags = "用户查询"` 标记,这样这两个方法在生成 Swagger 接口文档时候会分到一个共同的标签组里。 - -```java -import io.swagger.annotations.Api; -import io.swagger.annotations.ApiOperation; -import io.swagger.annotations.ApiParam; -import lombok.extern.slf4j.Slf4j; -import net.codingme.boot.domain.Response; -import net.codingme.boot.domain.User; -import net.codingme.boot.enums.ResponseEnum; -import net.codingme.boot.utils.ResponseUtill; -import org.springframework.validation.BindingResult; -import org.springframework.web.bind.annotation.*; - -import javax.validation.Valid; -import javax.validation.constraints.NotNull; -import java.util.ArrayList; -import java.util.List; - -/** - *

- * 用户操作 - * - * @Author niujinpeng - * @Date 2019/11/19 23:17 - */ - -@Slf4j -@RestController(value = "/v1") -@Api(value = "用户操作 API(v1)", tags = "用户操作接口") -public class UserController { - - @ApiOperation(value = "新增用户") - @PostMapping(value = "/user") - public Response create(@Valid User user, BindingResult bindingResult) throws Exception { - if (bindingResult.hasErrors()) { - String message = bindingResult.getFieldError().getDefaultMessage(); - log.info(message); - return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message); - } else { - // 新增用户信息 do something - return ResponseUtill.success("用户[" + user.getUsername() + "]信息已新增"); - } - } - - @ApiOperation(value = "删除用户") - @DeleteMapping(value = "/user/{username}") - public Response delete(@PathVariable("username") - @ApiParam(value = "用户名", required = true) String name) throws Exception { - // 删除用户信息 do something - return ResponseUtill.success("用户[" + name + "]信息已删除"); - } - - @ApiOperation(value = "修改用户") - @PutMapping(value = "/user") - public Response update(@Valid User user, BindingResult bindingResult) throws Exception { - if (bindingResult.hasErrors()) { - String message = bindingResult.getFieldError().getDefaultMessage(); - log.info(message); - return ResponseUtill.error(ResponseEnum.ERROR.getCode(), message); - } else { - String username = user.getUsername(); - return ResponseUtill.success("用户[" + username + "]信息已修改"); - } - } - - @ApiOperation(value = "获取单个用户信息", tags = "用户查询") - @GetMapping(value = "/user/{username}") - public Response get(@PathVariable("username") - @NotNull(message = "用户名称不能为空") - @ApiParam(value = "用户名", required = true) String username) throws Exception { - // 查询用户信息 do something - User user = new User(); - user.setId(10000); - user.setUsername(username); - user.setAge(99); - user.setSkills("cnp"); - return ResponseUtill.success(user); - } - - @ApiOperation(value = "获取用户列表", tags = "用户查询") - @GetMapping(value = "/user") - public Response selectAll() throws Exception { - // 查询用户信息列表 do something - User user = new User(); - user.setId(10000); - user.setUsername("未读代码"); - user.setAge(99); - user.setSkills("cnp"); - List userList = new ArrayList<>(); - userList.add(user); - return ResponseUtill.success(userList); - } -} -``` - -最后,为了让代码变得更加符合规范和好用,使用一个统一的类进行接口响应。 - -```java -@Data -@AllArgsConstructor -@NoArgsConstructor -@ApiModel(value = "响应信息") -public class Response { - /** - * 响应码 - */ - @ApiModelProperty(value = "响应码") - private String code; - /** - * 响应信息 - */ - @ApiModelProperty(value = "响应信息") - private String message; - - /** - * 响应数据 - */ - @ApiModelProperty(value = "响应数据") - private Collection content; -} -``` - -### 4.5. 运行访问 - -直接启动 Springboog 项目,可以看到控制台输出扫描到的各个接口的访问路径,其中就有 `/2/api-docs`。 - -![Springboot 启动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191126000341030.png) - -这个也就是生成的 OpenAPI 规范的描述 JSON 访问路径,访问可以看到。 - -![OpenAPI - JSON](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191126000613238.png) - -因为上面我们在引入依赖时,也引入了 springfox-swagger-ui 包,所以还可以访问 API 的页面文档。访问路径是 /swagger-ui.html,访问看到的效果可以看下图。 - -![swagger 访问](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191126000808167.png) - -也可以看到用户查询的两个方法会归到了一起,原因就是这两个方法的注解上使用相同的 tag 属性。 - -### 4.7. 调用测试 - -springfox-swagger-ui 不仅是生成了 API 文档,还提供了调用测试功能。下面是在页面上测试获取单个用户信息的过程。 - -1. 点击接口 [/user/{username}] 获取单个用户信息。 -2. 点击 `**Try it out** ` 进入测试传参页面。 -3. 输入参数,点击 **Execute** 蓝色按钮执行调用。 -4. 查看返回信息。 - -下面是测试时的响应截图。 - -![swagger 测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191126001337387.png) - -## 5. 常见报错 - -如果你在程序运行中经常发现像下面这样的报错。 - -```log -java.lang.NumberFormatException: For input string: "" - at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.8.0_111] - at java.lang.Long.parseLong(Long.java:601) ~[na:1.8.0_111] - at java.lang.Long.valueOf(Long.java:803) ~[na:1.8.0_111] - at io.swagger.models.parameters.AbstractSerializableParameter.getExample(AbstractSerializableParameter.java:412) ~[swagger-models-1.5.20.jar:1.5.20] - at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_111] - at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_111] - at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_111] - at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_111] - at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:536) [jackson-databind-2.5.4.jar:2.5.4] - at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:666) [jackson-databind-2.5.4.jar:2.5.4] - at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:156) [jackson-databind-2.5.4.jar:2.5.4] - at com.fasterxml.jackson.databind.ser.impl.IndexedListSerializer.serializeContents(IndexedListSerializer.java:113) [jackson-databind-2.5.4.jar:2.5.4] -``` - -那么你需要检查使用了 `@ApiModelProperty` 注解且字段类型为数字类型的属性上,`@ApiModelProperty` 注解是否设置了 example 值,如果没有,那就需要设置一下,像下面这样。 - -```java -@NotNull(message = "用户 ID 不能为空") -@ApiModelProperty(value = "用户ID", required = true, example = "1000") -private Integer id; -``` - -文中代码都已经上传到 [ https://github.com/niumoo/springboot ](https://github.com/niumoo/springboot) - -## 参考文档 - -- [OpenAPI Specification](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md) -- [Swagger Documentation](https://swagger.io/docs/specification/about/) -- [Springfox Reference Documentation](http://springfox.github.io/springfox/docs/current/) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-17-admin.md b/docs/springboot/springboot-17-admin.md deleted file mode 100644 index 0468a21..0000000 --- a/docs/springboot/springboot-17-admin.md +++ /dev/null @@ -1,489 +0,0 @@ ---- -title: Springboot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序 -toc_number: false -date: 2019-12-23 08:08:08 -url: springboot/springboot-17-admin -tags: -- Springboot -- 生产工具 -- Springboot Admin -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## 1. Spring Boot Admin 是什么 - -Spring Boot Admin 是由 [codecentric](github.com/codecentric) 组织开发的开源项目,使用 Spring Boot Admin 可以管理和监控你的 Spring Boot 项目。它分为客户端和服务端两部分,客户端添加到你的 Spring Boot 应用增加暴漏相关信息的 HTTP 接口,然后注册到 Spring Boot Admin 服务端,这一步骤可以直接向服务端注册,也可以通过 Eureka 或者 Consul 进行注册。而 Spring Boot Admin Server 通过 Vue.js 程序监控信息进行可视化呈现。并且支持多种事件通知操作。 - -## 2. Spring Boot Admin 服务端 - -Spring Boot Admin 服务端是基于 Spring Boot 项目的,如何创建一个 Spring Boot 项目这里不提,你可以参考之前文章或者从 [https://start.spring.io/](https://start.spring.io/) 直接获得一个 Spring Boot 项目。 - -### 2.1. 添加依赖 - -只需要添加 web 依赖和 Spring-boot-admin-starter-server 依赖。 - -```xml - - org.springframework.boot - spring-boot-starter-web - - - - de.codecentric - spring-boot-admin-starter-server - -``` - - - -### 2.2. 启动配置 - -为了和下面的客户端端口不冲突,先修改端口号为 9090。 - -```yml -server: - port: 9090 -``` - -添加 `@EnableAdminServer` 注解启用 Spring Boot Admin Server 功能。 - -```java -@EnableAdminServer -@SpringBootApplication -public class SpringbootAdminServerApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringbootAdminServerApplication.class, args); - } -} -``` - -服务端已经配置完成,启动项目进行访问就可以看到 Spring Boot Admin Server 的页面了。 - -![Spring Boot Admin Server UI](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191221204941292.png) - -## 3. Spring Boot Admin 客户端 - -创建 Spring Boot 项目依旧不提,这里只需要添加 Spring Boot Admin 客户端需要的依赖,在项目启动时就会增加相关的获取信息的 API 接口。然后在 Spring Boot 配置文件中配置 Spring Boot Admin 服务端,就可以进行监控了。 - -### 3.1 客户端依赖 - -pom.xml - -```xml - - org.springframework.boot - spring-boot-starter-web - - - de.codecentric - spring-boot-admin-starter-client - - - - org.projectlombok - lombok - true - -``` - -### 3.2 客户端配置 - -客户端配置主要为了让客户端可以成功向服务端注册,所以需要配置客户端所在应用相关信息以及 Spring Boot Admin Server 服务端的 url。 - -```yml -server: - port: 8080 - -spring: - application: - # 应用名称 - name: sjfx-api-search - jmx: - enabled: true - boot: - admin: - client: - # 服务端 url - url: http://127.0.0.1:9090 - instance: - # 客户端实例 url - service-url: http://127.0.0.1:8080 - prefer-ip: true - # 客户端实例名称 - name: sjfx-api-search - -management: - endpoints: - web: - exposure: - # 暴漏的接口 - 所有接口 - include: "*" -``` - -配置中的 `include: "*"` 公开了所有的端口,对于生产环境,应该自信的选择要公开的接口。 - -Spring Boot Admin 可以获取应用中的定时任务,所以在代码中增加一个定时任务计划,每 20 秒输出一次当前时间,日志级别为 `INFO`,用于下面的定时任务和日志监控测试。 - -```java -@Slf4j -@SpringBootApplication -@EnableScheduling -public class SpringbootAdminClientApplication { - - public static void main(String[] args) { - SpringApplication.run(SpringbootAdminClientApplication.class, args); - } - - @Scheduled(cron = "0/20 * * * * ?") - public void run20s() { - log.info("定时任务:{}", LocalDateTime.now()); - } -} -``` - -### 3.3. 客户端运行 - -启动客户端会暴漏相关的运行状态接口,并且自动向配置的服务端发送注册信息。 - -下面是客户端的启动日志: - -```log -2019-12-21 22:45:32.878 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : Starting SpringbootAdminClientApplication on DESKTOP-8SCFV4M with PID 13204 (D:\IdeaProjectMy\springboot-git\springboot-admin\springboot-admin-client\target\classes started by 83981 in D:\IdeaProjectMy\springboot-git\springboot-admin) -2019-12-21 22:45:32.881 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : No active profile set, falling back to default profiles: default -2019-12-21 22:45:33.627 INFO 13204 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) -2019-12-21 22:45:33.634 INFO 13204 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] -2019-12-21 22:45:33.634 INFO 13204 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.29] -2019-12-21 22:45:33.706 INFO 13204 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext -2019-12-21 22:45:33.706 INFO 13204 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 797 ms -2019-12-21 22:45:33.850 INFO 13204 --- [ main] o.s.b.a.e.web.ServletEndpointRegistrar : Registered '/actuator/jolokia' to jolokia-actuator-endpoint -2019-12-21 22:45:33.954 INFO 13204 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' -2019-12-21 22:45:34.089 INFO 13204 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService -2019-12-21 22:45:34.117 INFO 13204 --- [ main] o.s.s.c.ThreadPoolTaskScheduler : Initializing ExecutorService 'taskScheduler' -2019-12-21 22:45:34.120 INFO 13204 --- [ main] o.s.b.a.e.web.EndpointLinksResolver : Exposing 15 endpoint(s) beneath base path '/actuator' -2019-12-21 22:45:34.162 INFO 13204 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' -2019-12-21 22:45:34.163 INFO 13204 --- [ main] n.c.b.SpringbootAdminClientApplication : Started SpringbootAdminClientApplication in 1.563 seconds (JVM running for 2.131) -2019-12-21 22:45:34.271 INFO 13204 --- [gistrationTask1] d.c.b.a.c.r.ApplicationRegistrator : Application registered itself as 6bcf19a6bf8c -2019-12-21 22:45:34.293 INFO 13204 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' -2019-12-21 22:45:34.294 INFO 13204 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' -2019-12-21 22:45:34.300 INFO 13204 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 6 ms -``` - -从启动日志里的 `Exposing 15 endpoint(s) beneath base path '/actuator'` 这段,可以看到暴漏了 15 个 `/actuator` 的 API 接口,可以直接访问查看响应结果。 - -![Spring Boot Admin Client 监测接口](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191221224351221.png) - -从日志 `Application registered itself as 6bcf19a6bf8c` 可以看到客户端已经注册成功了。再看服务端可以看到注册上来的一个应用实例。 - -![Spring Boot Admin Server](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191221225022655.png) - -## 4. Spring Boot Admin 功能 - -点击监控页面上的在线的应用实例,可以跳转到应用实例详细的监控管理页面,也就是 Vue.js 实现的 web 展示。 - -![Spring Boot Admin Server 监控页面](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191222180411114.png) - -Spring Boot Admin Server 可以监控的功能很多,使用起来没有难度,下面描述下可以监测的部分内容: - -- 应用运行状态,如时间、垃圾回收次数,线程数量,内存使用走势。 -- 应用性能监测,通过选择 JVM 或者 Tomcat 参数,查看当前数值。 -- 应用环境监测,查看系统环境变量,应用配置参数,自动配置参数。 -- 应用 bean 管理,查看 Spring Bean ,并且可以查看是否单例。 -- 应用计划任务,查看应用的计划任务列表。 -- 应用日志管理,动态更改日志级别,查看日志。 -- 应用 JVM 管理,查看当前线程运行情况,dump 内存堆栈信息。 -- 应用映射管理,查看应用接口调用方法、返回类型、处理类等信息。 - -上面提到的日志管理,可以动态的更改日志级别,以及查看日志。默认配置下是只可以动态更改日志级别的,如果要在线查看日志,就需要手动配置日志路径了。 - -客户端上可以像下面这样配置日志路径以及日志高亮。 - -```yml -# 配置文件:application.yml -logging: - file: - name: boot.log - pattern: -# 日志高亮 - file: '%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(%5p) %clr(${PID}){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx' -``` - -下面是在 Spring Boot Admin 监测页面上查看的客户端应用日志。 - -![Spring Boot Admin Server 查看日志](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191222193212726.png) - -## 5. Spring Boot Admin 进阶 - -### 5.1. 邮件通知 - -Spring Boot Admin Server 支持常见的通知方式,比如邮件通知、电报通知、PagerDuty 通知等,下面将会演示常见的通知方式 - 邮件通知,最后也会演示如何通过监听时间进下设置自定义通知方式。 - -Spring Boot Admin Server 的邮件通知通过 Thymeleaf 模板发送 HTML 格式的电子邮件。因此,想要使用邮件通知首先要引入 Thymeleaf 依赖以及 `spring-boot-starter-mail` 依赖,并配置邮件发送者信息和接受者信息。 - - **1. 添加依赖** - -``` - - org.springframework.boot - spring-boot-starter-web - - - - de.codecentric - spring-boot-admin-starter-server - - - - org.springframework.boot - spring-boot-starter-mail - - - - - org.springframework.boot - spring-boot-starter-thymeleaf - -``` - -**2. 配置邮件** - -主要设置发送者信息和接收者信息。 - -``` -spring: - boot: - admin: - notify: - mail: - # 逗号分隔的邮件收件人列表 - to: xxxx@126.com - # 开启邮箱通知 - enabled: true - # 不需要发送通知的状态:从状态A:到状态B - ignore-changes: {"UNKNOWN:UP"} - # 逗号分隔的抄送收件人列表 - cc: xxxx@126.com - # 发件人 - from: Spring Boot Admin - -# 邮件发送者信息 - mail: - host: smtp.126.com - port: 25 - username: xxxx@126.com - default-encoding: utf-8 - password: xxxx -``` - -如果想了解更多关于 Spring Boot 邮件发送信息,可以参考 [Spring Boot 系列文章第十三篇](https://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483764&idx=1&sn=8cee8b1781b8659b3fdc23c0d650db49&chksm=e984e810def3610640e741eea1f94bbf95d4a2a5b1c68946829a036b4a6bc58b0d23e156474a&token=971841717&lang=zh_CN#rd)。 - -配置好邮件通知之后,重启服务端和客户端,等客户端注册到服务端之后直接终止客户端的运行,稍等片刻就可以在配置的通知接收邮箱里收到客户端实例下线通知了。 - -![Sping Boot Admin Server 邮件通知](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191222215118514.png) - -邮件通知使用的模板存放在 server 依赖的 classpath:/META-INF/spring-boot-admin-server/mail/status-changed.html 路径,如果想要自定义模板内容。可以拷贝这个文件放到自己的 templates 目录下,修改成自己想要的效果,然后在配置中指定自定义模板路径。 - -```yml -spring: - boot: - admin: - notify: - mail: - # 自定义邮件模版 - template: classpath:/templates/notify.html -``` - -### 5.2 自定义通知 - -自定义通知只需要自己实现 Spring Boot Admin Server 提供的监听通知类即可,下面会演示如何在实例状态改变时输出实例相关信息。 - -```java -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; - -import de.codecentric.boot.admin.server.domain.entities.Instance; -import de.codecentric.boot.admin.server.domain.entities.InstanceRepository; -import de.codecentric.boot.admin.server.domain.events.InstanceEvent; -import de.codecentric.boot.admin.server.domain.events.InstanceStatusChangedEvent; -import de.codecentric.boot.admin.server.notify.AbstractEventNotifier; -import de.codecentric.boot.admin.server.notify.LoggingNotifier; -import reactor.core.publisher.Mono; - -@Component -public class CustomNotifier extends AbstractEventNotifier { - - private static final Logger LOGGER = LoggerFactory.getLogger(LoggingNotifier.class); - - public CustomNotifier(InstanceRepository repository) { - super(repository); - } - - @Override - protected Mono doNotify(InstanceEvent event, Instance instance) { - return Mono.fromRunnable(() -> { - if (event instanceof InstanceStatusChangedEvent) { - LOGGER.info("Instance {} ({}) is {}", instance.getRegistration().getName(), event.getInstance(), - ((InstanceStatusChangedEvent)event).getStatusInfo().getStatus()); - } else { - LOGGER.info("Instance {} ({}) {}", instance.getRegistration().getName(), event.getInstance(), - event.getType()); - } - }); - } -} -``` - -### 5.2. 访问限制 - -上面提到过,因为客户端增加了暴漏运行信息的相关接口,所以在生产环境中使用存在风险,而服务端没有访问限制,谁的可以访问也是不合理的。 - -下面将会为客户端和服务端分别增加访问限制,客户端主要是限制敏感接口的访问权限,服务端则是全局的访问限制。这些访问限制都通过 spring 安全框架 security 来实现,所以首先要为客户端和服务端都增加 maven 依赖。 - -```xml - - org.springframework.boot - spring-boot-starter-security - -``` - -**1. 服务端** - -在引入安全框架依赖之后,需要配置访问控制,比如静态资源不需要限制,登录登出页面指定等。 - -```java -import java.util.UUID; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; -import org.springframework.security.web.util.matcher.AntPathRequestMatcher; -import de.codecentric.boot.admin.server.config.AdminServerProperties; -import io.netty.handler.codec.http.HttpMethod; - -@Configuration(proxyBeanMethods = false) -public class SecuritySecureConfig extends WebSecurityConfigurerAdapter { - - private final AdminServerProperties adminServer; - - public SecuritySecureConfig(AdminServerProperties adminServer) { - this.adminServer = adminServer; - } - - @Override - protected void configure(HttpSecurity http) throws Exception { - // @formatter:off - SavedRequestAwareAuthenticationSuccessHandler successHandler = new SavedRequestAwareAuthenticationSuccessHandler(); - successHandler.setTargetUrlParameter("redirectTo"); - successHandler.setDefaultTargetUrl(this.adminServer.path("/")); - - http.authorizeRequests() - .antMatchers(this.adminServer.path("https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/**")).permitAll() - .antMatchers(this.adminServer.path("/login")).permitAll() - .anyRequest().authenticated() - .and() - .formLogin().loginPage(this.adminServer.path("/login")).successHandler(successHandler).and() - .logout().logoutUrl(this.adminServer.path("/logout")).and() - .httpBasic().and() - .csrf() - .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) - .ignoringRequestMatchers( - new AntPathRequestMatcher(this.adminServer.path("/instances"), HttpMethod.POST.toString()), - new AntPathRequestMatcher(this.adminServer.path("/instances/*"), HttpMethod.DELETE.toString()), - new AntPathRequestMatcher(this.adminServer.path("/actuator/**")) - ) - .and() - .rememberMe().key(UUID.randomUUID().toString()).tokenValiditySeconds(1209600); - // @formatter:on - } - - // 代码配置用户名和密码的方式 - // Required to provide UserDetailsService for "remember functionality" - // @Override - // protected void configure(AuthenticationManagerBuilder auth) throws Exception { - // auth.inMemoryAuthentication().withUser("user").password("{noop}password").roles("USER"); - // } -} -``` - -在 application.yml 配置文件中配置用户名和密码。 - -```yml -spring: - security: - user: - name: user - password: 123 -``` - -重启服务端,再次访问就需要用户名和密码进行登录了。 - -![Spring Boot Admin Server 登录](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191222222408380.png) - -**2. 客户端** - -客户端在引入安全框架之后,也需要配置访问权限,主要是配置哪些路径可以访问,哪些路径访问需要登录限制,默认所有接口都需要登录限制。 - -同样的,客户端应用也需要在配置中配置客户端应用对于敏感接口的登录用户和密码,同时需要配置 Spring Boot Admin Server 的访问用户和密码,还要把自身的用户和密码注册时告诉服务端,不然服务端不能获取到监测数据。 - -```yml -spring: - security: - user: - # 客户端敏感接口用户和密码 - name: client - password: 123 - application: - # 应用名称 - name: sjfx-api-search - jmx: - enabled: true - boot: - admin: - client: - # 服务端 url - url: http://127.0.0.1:9090 - instance: - # 客户端实例 url - service-url: http://127.0.0.1:8080 - prefer-ip: true - # 客户端实例名称 - name: sjfx-api-search - metadata: - # 客户端自身的用户和密码告诉服务端 - user.name: client - user.password: 123 - # 服务端用户名密码 - username: user - password: 123 -``` - -客户端敏感接口访问测试。 - -![客户端应用访问](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191222232720674.png) - -到这里,客户端的敏感接口访问需要登录,服务端的管理页面也需要登录,客户端和服务端的访问控制已经完成了。 - -文中代码已经上传到:[github.com/niumoo/springboot/tree/master/springboot-admin](github.com/niumoo/springboot/tree/master/springboot-admin) - -**参考资料:** - -[https://github.com/codecentric/spring-boot-admin](https://github.com/codecentric/spring-boot-admin) - -[https://codecentric.github.io/spring-boot-admin/current/](https://codecentric.github.io/spring-boot-admin/current/) - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/springboot/springboot-18-module.md b/docs/springboot/springboot-18-module.md deleted file mode 100644 index caa1bec..0000000 --- a/docs/springboot/springboot-18-module.md +++ /dev/null @@ -1,472 +0,0 @@ ---- -title: 最详细的 Spring Boot 多模块开发与排坑指南 -date: 2020-03-19 08:08:08 -url: springboot/springboot-18-module -tags: -- Springboot -- 多模块 -- module -- 编译 -categories: -- Springboot ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/202003190904.png?x-oss-process=style/1000px) - -## 创建项目 - -创建一个 SpringBoot 项目非常的简单,简单到这里根本不用再提。你可以在使用 IDEA 新建项目时直接选择 `Spring Initlalize` 创建一个 Spring Boot 项目,也可以使用 Spring 官方提供的 Spring Boot 项目生成页面得到一个项目。 - -下面介绍一下使用 Spring 官方生成的方式,**如果你已经有了一个 Spring Boot 项目,这部分可以直接跳过**。 - -1. 打开 https://start.spring.io/ - -2. 填写 `group` 和 `Artifact` 信息,选择依赖(我选择了 Spring Web 和 Lombok )。 - - ![spring 官网创建初始项目](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200317215917.png?x-oss-process=style/1000px) - -3. 点击 `Generate` 按钮下载项目。 - -4. 打开下载的项目,删除无用的 `.mvn` 文件夹,`mvnw` 、 `mvnw.cmd` 、`HELP.md` 文件。 - -到这里已经得到了一个 Spring Boot 初始项目了,我们直接导入到 IDEA 中,看一眼 `pom.xml` 的内容。 - -```xml - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 2.2.5.RELEASE - - - com.wdbyte - springboot-module-demo - 0.0.1-SNAPSHOT - springboot-module-demo - Demo project for Spring Boot - - - 1.8 - - - - - org.springframework.boot - spring-boot-starter-web - - - - org.projectlombok - lombok - true - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - -``` - -把目录结构调整成自己想要的结构,然后添加 `controller` 和 `entity` 用于测试。 - -![项目目录结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200317223304.png) - -ProductController 类源代码。 - -```java -@RestController -@RequestMapping("/product") -public class ProductController { - - /** - * 获取商品列表 - * - * @return - */ - @GetMapping("/list") - public Map list() { - // 模拟查询商品逻辑 - Product product = new Product(); - product.setProductName("小米粥"); - product.setProductPrice(new BigDecimal(2.0)); - product.setProductStock(100); - - Map resultMap = new HashMap<>(); - resultMap.put("code", 000); - resultMap.put("message", "成功"); - resultMap.put("data", Arrays.asList(product)); - return resultMap; - } -} - -``` - -Product 类源代码。 - -```java -@Data -public class Product { - /** 商品名称. */ - private String productName; - /** 商品价格. */ - private BigDecimal productPrice; - /** 商品库存。 */ - private int productStock; -} -``` - -## 模块化 - -借助 IDEA 工具可以快速的把项目改造成 maven 多模块,这里我们把准备测试 demo 拆分为 common 和 web 两个模块,common 模块存放实体类。web 模块存放 controller 层(这里项目虽小,拆分只是为了演示)。话不多说,直接开始。 - -1. 配置主 pom.xml **打包方式 为 pom** - - ```xml - - - 4.0.0 - - pom - .... - .... - ``` - -2. 创建 common 模块 - - 项目直接 new -> module。 - - ![创建模块](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200317224122.png) - - 选择 maven -> next,填写模块名称。 - - ![填写模块名称](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200317224304.png?x-oss-process=style/1000px) - - 继续 next 完成模块创建。 - -3. 创建 web 模块 - - web 模块的创建和 common 模块如出一辙,不再赘述。完成两个模块的创建之后,你会发现你的主 pom.xml 文件里自动添加了 module 部分。 - - ```xml - - product-common - product-web - - ``` - -4. 移动代码到指定模块 - - 移动 `Product.java` 到 `product-common` 模块,其他部分代码和 resource 部分直接移动到 `product-web` 模块,移动完后你的代码结构是这个样子。 - - ![多模块目录结构](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200318222623.png) - -到这里,多模块已经拆分完成了, 但是 `ProductController` 代码里的红色警告让你发现事情还没有结束。 - -## 依赖管理 - -### 处理依赖问题 - -你发现了代码里的红色警告,不过你也瞬间想到了是因为把 `Product ` 类移动到了 `product-common` 模块,导致这里引用不到了。 - -![红色警告](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200318223007.png?x-oss-process=style/1000px) - -然后你查看了下 `product-common` 模块的 pom.xml 里的内容。 - -```xml - - - - springboot-module-demo - com.wdbyte - 0.0.1-SNAPSHOT - - 4.0.0 - product-common - -``` - -机智的在 `Product-web` 模块的 pom.xml 里引入 product-common,手起键落,轻松搞定。 - -```xml - - - - springboot-module-demo - com.wdbyte - 0.0.1-SNAPSHOT - - 4.0.0 - product-web - - - com.wdbyte - product-common - - - -``` - -满心欢喜的你快速的点击 Build-> Build Project,得到的 Error 警告刺痛了顶着黑眼圈的你。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200318223732.png?x-oss-process=style/1000px) - -不过你还是迅速定位了问题,查看 maven 依赖,你发现是因为没有指定 `product-common` 依赖的版本号。 - -![报错信息](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200318224039.png?x-oss-process=style/1000px) - -原来如此,因为没有指定版本号,我们指定上不就完事了嘛。在最外层的主 pom.xml 中添加 `` 添加上指定依赖和要指定的版本号。 - -```xml - - - - com.wdbyte - product-common - 0.0.1-SNAPSHOT - - - -``` - -刷新 maven ,发现项目已经不报错了,编译成功,运行启动类,熟悉的 Spring logo 又出现在眼前。 - -### 优化依赖 - -是的,Spring Boot 应用在改造成多模块后成功运行了起来,但是你貌似发现一个问题,模块 `common` 和模块 `web` 都继承了主 pom ,主 pom 中有 Lombok 、Spring Boot Web 和 Spring Boot Test 依赖,而 `common` 模块里只用到了 Lombok 啊,却一样继承了 Spring Boot 其他依赖,看来还是要改造一把。 - -1. 只有 `common` 模块用到的依赖移动到 `common` 模块。 - - ```xml - - - - springboot-module-demo - com.wdbyte - 0.0.1-SNAPSHOT - - 4.0.0 - product-common - - - org.projectlombok - lombok - true - - - - ``` - -2. 只有 `web` 模块用到的依赖移动到 `web` 模块。 - - ```xml - - - - springboot-module-demo - com.wdbyte - 0.0.1-SNAPSHOT - - 4.0.0 - product-web - - - - com.wdbyte - product-common - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.junit.vintage - junit-vintage-engine - - - - - - ``` - -3. 抽取用到的版本号到 ``,这里抽取 `common` 模块的依赖版本。 - - 到这里最外层主 pom 的内容是这样的。 - - ``` xml - - - 4.0.0 - pom - - product-common - product-web - - - org.springframework.boot - spring-boot-starter-parent - 2.2.5.RELEASE - - - com.wdbyte - springboot-module-demo - 0.0.1-SNAPSHOT - springboot-module-demo - Demo project for Spring Boot - - - 1.8 - 0.0.1-SNAPSHOT - - - - - - com.wdbyte - product-common - ${product-common.version} - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - - - ``` - -**看似完美**,重新 Build-> Build Project ,发现一切正常,运行发现一切正常,访问正常。 - -![访问接口](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200318232809.png?x-oss-process=style/1000px) - -## 打包编译 - -好了,终于到了最后一步了,你感觉到胜利的曙光已经照到了头顶,反射出耀眼的光芒。接着就是 `mvn package `。 - -```shell -[INFO] springboot-module-demo ............................. SUCCESS [ 2.653 s] -[INFO] product-common ..................................... FAILURE [ 2.718 s] -[INFO] product-web ........................................ SKIPPED -[INFO] ------------------------------------------------------------------------ -[INFO] BUILD FAILURE -[INFO] ------------------------------------------------------------------------ -[INFO] Total time: 6.084 s -[INFO] Finished at: 2020-03-19T08:15:52+08:00 -[INFO] Final Memory: 22M/87M -[INFO] ------------------------------------------------------------------------ -[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.2.5.RELEASE:repackage (repackage) on project product-common: Execution repackage of goal org.springframework.boot:spring-boot-m -aven-plugin:2.2.5.RELEASE:repackage failed: Unable to find main class -> [Help 1] -[ERROR] -``` - -**ERROR** 让你伤心了,但是你还是从报错中寻找到了一些蛛丝马迹,你看到是 spring-boot-maven-plugin 报出的错误。重新审视你的主 pom 发现 `` 编译插件用到了 spring-boot-maven-plugin。 - -```xml - - - - org.springframework.boot - spring-boot-maven-plugin - - - -``` - -略加思索后将这段移动到 `web` 模块的 pom,因为这是 Spring Boot 的打包方式,现在放在主 pom 中所有的模块都会继承到,那么对于 `common` 模块来说是肯定不需要的。 - -移动后重新打包,不管你是运行命令 `mvn package` 还是双击 IDEA 中的 maven 管理中的 package ,想必这时候你都已经打包成功了 - -![IDEA 打包](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/20200319082547.png?x-oss-process=style/1000px) - -在 `web` 模块下的目录 target 里也可以看到打包后的 jar 文件 product-web-0.0.1-SNAPSHOT.jar。可以使用 java 命令直接运行。 - -```shell -$ \springboot-module-demo\product-web\target>java -jar product-web-0.0.1-SNAPSHOT.jar - - . ____ _ __ _ _ - /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ -( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ - \\/ ___)| |_)| | | | | || (_| | ) ) ) ) - ' |____| .__|_| |_|_| |_\__, | / / / / - =========|_|==============|___/=/_/_/_/ - :: Spring Boot :: (v2.2.5.RELEASE) - -2020-03-19 08:33:03.337 INFO 15324 --- [ main] com.wdbyte.Application : Starting Application v0.0.1-SNAPSHOT on DESKTOP-8SCFV4M with PID 15324 (C:\Users\83981\Desktop\springboot-mod -ule-demo\product-web\target\product-web-0.0.1-SNAPSHOT.jar started by 83981 in C:\Users\83981\Desktop\springboot-module-demo\product-web\target) -2020-03-19 08:33:03.340 INFO 15324 --- [ main] com.wdbyte.Application : No active profile set, falling back to default profiles: default -2020-03-19 08:33:04.410 INFO 15324 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) -2020-03-19 08:33:04.432 INFO 15324 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] -2020-03-19 08:33:04.432 INFO 15324 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.31] -2020-03-19 08:33:04.493 INFO 15324 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext -2020-03-19 08:33:04.493 INFO 15324 --- [ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1107 ms -2020-03-19 08:33:04.636 INFO 15324 --- [ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService 'applicationTaskExecutor' -2020-03-19 08:33:04.769 INFO 15324 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' -2020-03-19 08:33:04.772 INFO 15324 --- [ main] com.wdbyte.Application : Started Application in 1.924 seconds (JVM running for 2.649) -2020-03-19 08:33:07.087 INFO 15324 --- [extShutdownHook] o.s.s.concurrent.ThreadPoolTaskExecutor : Shutting down ExecutorService 'applicationTaskExecutor' -``` - -想必少了点什么,多模块不仅为了结构清晰,更是为了其他项目可以**复用模块**(如 common 模块),现在这个时候如果你新打开了一个项目,依赖 `common ` 发现是引用不到的,因为你需要把模块安装到本地仓库。可以点击 IDEA -> Maven -> install,也可以通过 maven 命令。 - -```java -# -Dmaven.test.skip=true 跳过测试 -# -U 强制刷新 -# clean 清理缓存 -# install 安装到本地仓库 -$ \springboot-module-demo> mvn -Dmaven.test.skip=true -U clean install -``` - -重新引入发现没有问题了。 -文中代码已经上传到 Github: [https://github.com/niumoo/springboot](https://github.com/niumoo/springboot) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-Lombok.md b/docs/tool/tool-Lombok.md deleted file mode 100644 index 8b342ce..0000000 --- a/docs/tool/tool-Lombok.md +++ /dev/null @@ -1,137 +0,0 @@ ---- -title: 如何使用 Lombok 进行优雅的编码 -date: 2018-12-30 00:08:10 -url: develop/tool-Lombok -tags: - - Lombok - - 开发工具 -categories: - - 生产工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -Project Lombok 是一个 java 库,它可以通过注解自动为你要编写的类添加相应功能,如 get/set 方法,提高了开发效率。 - -引入 POM 依赖。 - -```xml - - - org.projectlombok - lombok - 1.18.4 - provided - - - - ch.qos.logback - logback-classic - 1.0.6 - -``` - - -查看依赖关系可以发现 `logback-classic` 依赖了 `SLF4J-API` 日志门面以及 l`ogback-core` 日志实现框架。 - -![添加的pom依赖](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1544414938730.png) -关于日志注解的选择,可以参考[官方文档](https://www.projectlombok.org/features/log)。默认的日志输出注释类为被注释的类路径,也可以使用 topic 参数自定义,如`@Slf4j(topic="reporting")`. 一般我们都会选择 `@Slf4j` 这个日志抽象类。在使用这个注解的时候需要导入 SLF4-API 抽象层以及具体的日志实现框架,上方的依赖中我们已经添加了日志依赖。 - -```java -@CommonsLog -Creates private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(LogExample.class); -@Flogger -Creates private static final com.google.common.flogger.FluentLogger log = com.google.common.flogger.FluentLogger.forEnclosingClass(); -@JBossLog -Creates private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LogExample.class); -@Log -Creates private static final java.util.logging.Logger log = java.util.logging.Logger.getLogger(LogExample.class.getName()); -@Log4j -Creates private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(LogExample.class); -@Log4j2 -Creates private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(LogExample.class); -@Slf4j -Creates private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogExample.class); -@XSlf4j -Creates private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(LogExample.class); -``` -`Lombok` 的使用主要是几个注解,下面介绍常用的几个注解。 - -`@Getter/@Setter` 为属性生成 get 和 set 方法。 - -`@ToString` 生成 toString 方法,输出各个属性值。 - -`@EqualsAndHashCode` 生成 equals 和 hashCode 方法。 - -`@NoArgsConstructor` 生成无惨构造器。 - -`@AllArgsConstructor` 生成全参数构造器。 - -`@Data` 是一个方便注解,它捆绑了 `@ToString` `@Getter/@Setter` `@EqualsAndHashCode` 以及 `@RequiredArgsConstructor`. - -根据上面的解释,用下面的一个例子演示用法。 - -```java -import lombok.*; -import lombok.extern.slf4j.Slf4j; - -/** - *

- * Lombok使用 - * - * @Author niujinpeng - * @Date 2018/12/10 11:05 - */ -@Slf4j(topic = "Lombok") -public class LombokTest { - - public static void main(String[] args) { - Person person1 = new Person(); - person1.setName("Darcy"); - person1.setAge(22); - person1.setTeacher(true); - log.info(person1.toString()); - - Person person2 = new Person("Darcy", 22, true); - log.info(person2.toString()); - - boolean equals = person1.equals(person2); - log.info("Equals:" + equals); - log.info(person1.hashCode() + " and " + person2.hashCode()); - } -} - -//@Getter -//@Setter -//@ToString -//@EqualsAndHashCode -@Data -@NoArgsConstructor -@AllArgsConstructor -class Person { - private String name; - private Integer age; - private boolean isTeacher; -} -``` - -可以看到 Person 类除了几个属性定义之外没有其他任何方法代码,运行 LombokTest 可以在控制台看到输出如下。 - -```log -12:12:57.687 [main] INFO Lombok - Person(name=Darcy, age=22, isTeacher=true) -12:12:57.690 [main] INFO Lombok - Person(name=Darcy, age=22, isTeacher=true) -12:12:57.690 [main] INFO Lombok - Equals:true -12:12:57.690 [main] INFO Lombok - 1423350487 and 1423350487 - -Process finished with exit code 0 -``` - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) diff --git a/docs/tool/tool-apache-ant.md b/docs/tool/tool-apache-ant.md deleted file mode 100644 index 66cf6bf..0000000 --- a/docs/tool/tool-apache-ant.md +++ /dev/null @@ -1,274 +0,0 @@ ---- -title: 使用Apache Ant 进行Java web项目打包并部署至TOMCAT -date: 2017-11-01 18:58:23 -updated: 2017-11-01 18:58:23 -url: develop/tool-apache-ant -categories: - - 生产工具 -tags: -- Tomcat -- Ant -- 开发工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -### Apache Ant介绍 -是一个将软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。由Apache软件基金会所提供。 - -**优点**:Ant是Apache软件基金会JAKARTA目录中的一个子项目,它有以下的优点。跨平台性。Ant是纯Java语言编写的,所以具有很好的跨平台性。操作简单。Ant是由一个内置任务和可选任务组成的。Ant运行时需要一个XML文件(构建文件)。 Ant通过调用target树,就可以执行各种task。每个task实现了特定接口对象。由于Ant构建文件 是XML格式的文件,所以很容易维护和书写,而且结构很清晰。由于Ant的跨平台性和操作简单的特点,它很容易集成到一些开发环 境中去。 - - - **那么我们如何使用呢?这里用一个例子进行演示如何用ANT来编译一个Java web项目。** -### 下载Apache Ant - - 直接到[官方网站](http://ant.apache.org/)下载, -或者直接: ->[Windows版本](https://mirrors.tuna.tsinghua.edu.cn/apache//ant/binaries/apache-ant-1.9.9-bin.zip) -> ->[Linux版本](https://mirrors.tuna.tsinghua.edu.cn/apache//ant/binaries/apache-ant-1.9.9-bin.tar.gz) - -### 安装Apache Ant -直接解压下载的压缩包,可以看到Ant的目录结构。 - -Bin目录:Ant命令 -Lib目录:Ant所需要的jar包 -manual:用户参考文档 - -### 配置Apache Ant - -1:首先需要安装了JDK,并且配置环境变量。 -2:配置ANT_HOME/bin目录即可。(自行配置) -3:测试是否配置成功。 -运行命令ant -version查看版本号,配置成功可以看到: -```shell -C:\>ant -version -Apache Ant(TM) version 1.9.9 compiled on February 2 2017 -C:\> -``` - -### Apache Ant编译准备 -这里用一个Java web项目为例。 -看下JavaWeb项目整体目录结构。 - -```shell -├─.settings -├─src -│ └─net -│ └─codingme -│ ├─controller -│ ├─dao -│ ├─mapping -│ ├─po -│ ├─service -│ │ └─impl -│ └─util -└─WebContent - ├─attached - ├─css - ├─fonts - ├─js - │ └─google-code-prettify - ├─META-INF - └─WEB-INF - ├─lib - └─view - └─post -``` -#### 编写用于编译的build.xml - ![enter image description here](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/6b6e8cf5d117335e278227d6b6d0de0d.jpg) - -build.xml具体内容。 这里已经把注释已经写的非常清楚,具体内容如下。 - -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 初始化工作结束! - - - - - - - - - 初始化工作结束! - - - - - - - - - - 打包完成! - - - - - - - - 已发布到Tomcat! - - - - - - - 清理完成! - - - -``` -#### Build.xml文件的一些解释 - -```xml - -``` - -build.xml中的第一句话,没有实际的意义。 - -```xml - - -``` - -包含Ant的所有内容,name即名字,basedir即工作根目录,default即默认操作。 - -```xml - -``` - -类似于声明变量:即src.dir代表localtion里配置的目录。 - -```xml - - - - - - - 打包完成! - -``` - - -Ant每一件要做的事情我们都需要写 成**target**的形式,并给出一个名字name,depends="compile"表示在做这个target之前需要先执行compile,description即描述。 -中间则是打包成war包的格式需要。 - -参考上面的build.xml文件,我相信可以解决很多简单的编译问题。 -但是Ant的功能远远不止于此,其他参数可以参考[官方文档](http://ant.apache.org/manual-1.9.x/index.html) - -### Apache Ant编译开始 -在做了上面的准备工作之后,我们离成功只差一步之遥了。 -命令切换到build.xml文件所在目录。 -查看当前目录里build.xml里编写的功能 -用命令 ant -p(在执行ant命令时默认会使用名字为build.xml的文件) -```shell -C:\Users\83981\Desktop\Apache Ant\BlogV2>ant -p -Buildfile: C:\Users\83981\Desktop\Apache Ant\BlogV2\build.xml -Main targets: - clean 清理 - compile 编译 - deploy 发布 - init 初始化 - war 打包war文件 -Default target: run - -C:\Users\83981\Desktop\Apache Ant\BlogV2> -``` -Ant的常用操作: -ant target_name 执行相应的操作。 -ant clean 清理文件夹。 -ant deploy 进行发布。 - -```shell -C:\Users\83981\Desktop\Apache Ant\BlogV2>ant clean -Buildfile: C:\Users\83981\Desktop\Apache Ant\BlogV2\build.xml - -clean: - [echo] 清理完成! - -BUILD SUCCESSFUL -Total time: 0 seconds - -C:\Users\83981\Desktop\Apache Ant\BlogV2>ant deploy -Buildfile: C:\Users\83981\Desktop\Apache Ant\BlogV2\build.xml - -init: - [mkdir] Created dir: C:\Users\83981\Desktop\Apache Ant\BlogV2\build - [mkdir] Created dir: C:\Users\83981\Desktop\Apache Ant\BlogV2\build\classes - [mkdir] Created dir: C:\Users\83981\Desktop\Apache Ant\BlogV2\build\war - [echo] 初始化工作结束! - -compile: - [javac] Compiling 30 source files to C:\Users\83981\Desktop\Apache Ant\BlogV2\build\classes - [echo] 初始化工作结束! - [echo] 编译完成! - -war: - [war] Building war: C:\Users\83981\Desktop\Apache Ant\BlogV2\build\war\BlogV2.war - [echo] 打包完成! - -deploy: - [copy] Copying 1 file to D:\CodeProgram\tomcat7\webContent - [echo] 已发布到Tomcat! - -BUILD SUCCESSFUL -Total time: 6 seconds - -C:\Users\83981\Desktop\Apache Ant\BlogV2> -``` - -项目已经编译并打包成war格式拷贝至指定的tomcat目录 -操作结束,关于Ant的其他命令可以执行ant -help进行查看。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-arthas.md b/docs/tool/tool-arthas.md deleted file mode 100644 index 73a9f37..0000000 --- a/docs/tool/tool-arthas.md +++ /dev/null @@ -1,865 +0,0 @@ ---- -title: Arthas - Java 线上问题定位处理的终极利器 -toc_number: false -date: 2019-11-06 02:15:08 -url: arthas -tags: - - Arthas - - 性能分析 -categories: - - 生产工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![Arthas logo](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/arthas-1572972116473.png) - -# 前言 - -在使用 **Arthas** 之前,当遇到 Java 线上问题时,如 CPU 飙升、负载突高、内存溢出等问题,你需要查命令,查网络,然后 jps、jstack、jmap、jhat、jstat、hprof 等一通操作。最终焦头烂额,还不一定能查出问题所在。而现在,大多数的常见问题你都可以使用 **Arthas** 轻松定位,迅速解决,及时止损,准时下班。 - -# 1、Arthas 介绍 - - -**Arthas** 是 `Alibaba` 在 2018 年 9 月开源的 **Java 诊断**工具。支持 `JDK6+`, 采用命令行交互模式,提供 `Tab` 自动不全,可以方便的定位和诊断线上程序运行问题。截至本篇文章编写时,已经收获 `Star` 17000+。 - -**Arthas** 官方文档十分详细,本文也参考了官方文档内容,同时在开源在的 `Github` 的项目里的 `Issues` 里不仅有问题反馈,更有大量的使用案例,也可以进行学习参考。 - -开源地址:*https://github.com/alibaba/arthas* - -官方文档:*https://alibaba.github.io/arthas* - - - -# 2、Arthas 使用场景 - -得益于 **Arthas** 强大且丰富的功能,让 **Arthas** 能做的事情超乎想象。下面仅仅列举几项常见的使用情况,更多的使用场景可以在熟悉了 **Arthas** 之后自行探索。 - -1. 是否有一个全局视角来查看系统的运行状况? -2. 为什么 CPU 又升高了,到底是哪里占用了 CPU ? -3. 运行的多线程有死锁吗?有阻塞吗? -4. 程序运行耗时很长,是哪里耗时比较长呢?如何监测呢? -5. 这个类从哪个 jar 包加载的?为什么会报各种类相关的 Exception? -6. 我改的代码为什么没有执行到?难道是我没 commit?分支搞错了? -7. 遇到问题无法在线上 debug,难道只能通过加日志再重新发布吗? -8. 有什么办法可以监控到 JVM 的实时运行状态? - -# 3、Arthas 怎么用 - -前文已经提到,**Arthas** 是一款命令行交互模式的 Java 诊断工具,由于是 Java 编写,所以可以直接下载相应 的 jar 包运行。 - -## 3.1 安装 - -可以在官方 Github 上进行下载,如果速度较慢,可以尝试国内的码云 Gitee 下载。 - -```shell -# github下载 -wget https://alibaba.github.io/arthas/arthas-boot.jar -# 或者 Gitee 下载 -wget https://arthas.gitee.io/arthas-boot.jar -# 打印帮助信息 -java -jar arthas-boot.jar -h -``` -## 3.2 运行 - -**Arthas** 只是一个 java 程序,所以可以直接用 `java -jar` 运行。运行时或者运行之后要选择要监测的 Java 进程。 - -```shell -# 运行方式1,先运行,在选择 Java 进程 PID -java -jar arthas-boot.jar -# 选择进程(输入[]内编号(不是PID)回车) -[INFO] arthas-boot version: 3.1.4 -[INFO] Found existing java process, please choose one and hit RETURN. -* [1]: 11616 com.Arthas - [2]: 8676 - [3]: 16200 org.jetbrains.jps.cmdline.Launcher - [4]: 21032 org.jetbrains.idea.maven.server.RemoteMavenServer - -# 运行方式2,运行时选择 Java 进程 PID -java -jar arthas-boot.jar [PID] -``` -查看 PID 的方式可以通过 `ps` 命令,也可以通过 JDK 提供的 `jps`命令。 - -```shell -# 查看运行的 java 进程信息 -$ jps -mlvV -# 筛选 java 进程信息 -$ jps -mlvV | grep [xxx] -``` - -`jps` 筛选想要的进程方式。 - -![jps 筛选进程](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1570979767404.png) - -在出现 **Arthas** Logo 之后就可以使用命令进行问题诊断了。下面会详细介绍。 - -![Arthas 启动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191106003512451.png) - -更多的启动方式可以参考 help 帮助命令。 - -```shell -# 其他用法 -EXAMPLES: - java -jar arthas-boot.jar - java -jar arthas-boot.jar --target-ip 0.0.0.0 - java -jar arthas-boot.jar --telnet-port 9999 --http-port -1 - java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' - java -jar arthas-boot.jar --tunnel-server 'ws://192.168.10.11:7777/ws' ---agent-id bvDOe8XbTM2pQWjF4cfw - java -jar arthas-boot.jar --stat-url 'http://192.168.10.11:8080/api/stat' - java -jar arthas-boot.jar -c 'sysprop; thread' - java -jar arthas-boot.jar -f batch.as - java -jar arthas-boot.jar --use-version 3.1.4 - java -jar arthas-boot.jar --versions - java -jar arthas-boot.jar --session-timeout 3600 - java -jar arthas-boot.jar --attach-only - java -jar arthas-boot.jar --repo-mirror aliyun --use-http -``` - -## 3.3 web console - -**Arthas** 目前支持 `Web Console`,在成功启动连接进程之后就已经自动启动,可以直接访问 http://127.0.0.1:8563/ 访问,页面上的操作模式和控制台完全一样。 - -![1570979937637](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1570979937637.png) - - - -## 3.4 常用命令 - -下面列举一些 [**Arthas**](https://www.wdbyte.com/2019/11/arthas/) 的常用命令,看到这里你可能还不知道怎么使用,别急,后面会一一介绍。 - -| 命令 | 介绍 | -| ------------------------------------------------------------ | ------------------------------------------------------------ | -| [dashboard](https://alibaba.github.io/arthas/dashboard.html) | 当前系统的实时数据面板 | -| [**thread**](https://alibaba.github.io/arthas/thread.html) | 查看当前 JVM 的线程堆栈信息 | -| [**watch**](https://alibaba.github.io/arthas/watch.html) | 方法执行数据观测 | -| **[trace](https://alibaba.github.io/arthas/trace.html)** | 方法内部调用路径,并输出方法路径上的每个节点上耗时 | -| [**stack**](https://alibaba.github.io/arthas/stack.html) | 输出当前方法被调用的调用路径 | -| [**tt**](https://alibaba.github.io/arthas/tt.html) | 方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 | -| [monitor](https://alibaba.github.io/arthas/monitor.html) | 方法执行监控 | -| [jvm](https://alibaba.github.io/arthas/jvm.html) | 查看当前 JVM 信息 | -| [vmoption](https://alibaba.github.io/arthas/vmoption.html) | 查看,更新 JVM 诊断相关的参数 | -| [sc](https://alibaba.github.io/arthas/sc.html) | 查看 JVM 已加载的类信息 | -| [sm](https://alibaba.github.io/arthas/sm.html) | 查看已加载类的方法信息 | -| [jad](https://alibaba.github.io/arthas/jad.html) | 反编译指定已加载类的源码 | -| [classloader](https://alibaba.github.io/arthas/classloader.html) | 查看 classloader 的继承树,urls,类加载信息 | -| [heapdump](https://alibaba.github.io/arthas/heapdump.html) | 类似 jmap 命令的 heap dump 功能 | - -## 3.5 退出 - -使用 shutdown 退出时 **Arthas** 同时自动重置所有增强过的类 。 - -# 4、Arthas 常用操作 - -上面已经了解了什么是 **Arthas**,以及 **Arthas** 的启动方式,下面会依据一些情况,详细说一说 **Arthas** 的使用方式。在使用命令的过程中如果有问题,每个命令都可以是 `-h` 查看帮助信息。 - -首先编写一个有各种情况的测试类运行起来,再使用 **Arthas** 进行问题定位, - -```java -import java.util.HashSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import lombok.extern.slf4j.Slf4j; - -/** - *

- * Arthas Demo - * 公众号:未读代码 - * - * @Author niujinpeng - */ -@Slf4j -public class Arthas { - - private static HashSet hashSet = new HashSet(); - /** 线程池,大小1*/ - private static ExecutorService executorService = Executors.newFixedThreadPool(1); - - public static void main(String[] args) { - // 模拟 CPU 过高,这里注释掉了,测试时可以打开 - // cpu(); - // 模拟线程阻塞 - thread(); - // 模拟线程死锁 - deadThread(); - // 不断的向 hashSet 集合增加数据 - addHashSetThread(); - } - - /** - * 不断的向 hashSet 集合添加数据 - */ - public static void addHashSetThread() { - // 初始化常量 - new Thread(() -> { - int count = 0; - while (true) { - try { - hashSet.add("count" + count); - Thread.sleep(10000); - count++; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - } - - public static void cpu() { - cpuHigh(); - cpuNormal(); - } - - /** - * 极度消耗CPU的线程 - */ - private static void cpuHigh() { - Thread thread = new Thread(() -> { - while (true) { - log.info("cpu start 100"); - } - }); - // 添加到线程 - executorService.submit(thread); - } - - /** - * 普通消耗CPU的线程 - */ - private static void cpuNormal() { - for (int i = 0; i < 10; i++) { - new Thread(() -> { - while (true) { - log.info("cpu start"); - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - } - } - - /** - * 模拟线程阻塞,向已经满了的线程池提交线程 - */ - private static void thread() { - Thread thread = new Thread(() -> { - while (true) { - log.debug("thread start"); - try { - Thread.sleep(3000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }); - // 添加到线程 - executorService.submit(thread); - } - - /** - * 死锁 - */ - private static void deadThread() { - /** 创建资源 */ - Object resourceA = new Object(); - Object resourceB = new Object(); - // 创建线程 - Thread threadA = new Thread(() -> { - synchronized (resourceA) { - log.info(Thread.currentThread() + " get ResourceA"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - log.info(Thread.currentThread() + "waiting get resourceB"); - synchronized (resourceB) { - log.info(Thread.currentThread() + " get resourceB"); - } - } - }); - - Thread threadB = new Thread(() -> { - synchronized (resourceB) { - log.info(Thread.currentThread() + " get ResourceB"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - log.info(Thread.currentThread() + "waiting get resourceA"); - synchronized (resourceA) { - log.info(Thread.currentThread() + " get resourceA"); - } - } - }); - threadA.start(); - threadB.start(); - } -} -``` - - - -## 4.1 全局监控 - -使用 **dashboard** 命令可以概览程序的 线程、内存、GC、运行环境信息。 - -![dashboard](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571212470373.png) - -## 4.2 CPU 为什么起飞了 - -上面的代码例子有一个 `CPU` 空转的死循环,非常的消耗 `CPU性能`,那么怎么找出来呢? - -使用 **thread**查看**所有**线程信息,同时会列出每个线程的 `CPU` 使用率,可以看到图里 ID 为12 的线程 CPU 使用100%。 -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1570983440457.png) - -使用命令 **thread 12** 查看 CPU 消耗较高的 12 号线程信息,可以看到 CPU 使用较高的方法和行数(这里的行数可能和上面代码里的行数有区别,因为上面的代码在我写文章时候重新排过版了)。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1570983401254.png) - -上面是先通过观察总体的线程信息,然后查看具体的线程运行情况。如果只是为了寻找 CPU 使用较高的线程,可以直接使用命令 **thread -n [显示的线程个数]** ,就可以排列出 CPU 使用率 **Top N** 的线程。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1570983061047.png) - -定位到的 CPU 使用最高的方法。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571016675083.png) - -## 4.3 线程池线程状态 - -定位线程问题之前,先回顾一下线程的几种常见状态: - -- **RUNNABLE** 运行中 -- **TIMED_WAITIN** 调用了以下方法的线程会进入**TIMED_WAITING**: - 1. Thread#sleep() - 2. Object#wait() 并加了超时参数 - 3. Thread#join() 并加了超时参数 - 4. LockSupport#parkNanos() - 5. LockSupport#parkUntil() -- **WAITING** 当线程调用以下方法时会进入WAITING状态: - 1. Object#wait() 而且不加超时参数 - 2. Thread#join() 而且不加超时参数 - 3. LockSupport#park() -- **BLOCKED** 阻塞,等待锁 - -上面的模拟代码里,定义了线程池大小为1 的线程池,然后在 `cpuHigh` 方法里提交了一个线程,在 `thread`方法再次提交了一个线程,后面的这个线程因为线程池已满,会阻塞下来。 - -使用 **thread | grep pool** 命令查看线程池里线程信息。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571020871537.png) - -可以看到线程池有 **WAITING** 的线程。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571021838323.png) - -## 4.4 线程死锁 - -上面的模拟代码里 `deadThread `方法实现了一个死锁,使用 **thread -b** 命令查看直接定位到死锁信息。 - -```java -/** - * 死锁 - */ -private static void deadThread() { - /** 创建资源 */ - Object resourceA = new Object(); - Object resourceB = new Object(); - // 创建线程 - Thread threadA = new Thread(() -> { - synchronized (resourceA) { - log.info(Thread.currentThread() + " get ResourceA"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - log.info(Thread.currentThread() + "waiting get resourceB"); - synchronized (resourceB) { - log.info(Thread.currentThread() + " get resourceB"); - } - } - }); - - Thread threadB = new Thread(() -> { - synchronized (resourceB) { - log.info(Thread.currentThread() + " get ResourceB"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - log.info(Thread.currentThread() + "waiting get resourceA"); - synchronized (resourceA) { - log.info(Thread.currentThread() + " get resourceA"); - } - } - }); - threadA.start(); - threadB.start(); -} -``` - -检查到的死锁信息。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571206638142.png) - - - -## 4.5 反编译 - -上面的代码放到了包 `com`下,假设这是一个线程环境,当怀疑当前运行的代码不是自己想要的代码时,可以直接反编译出代码,也可以选择性的查看类的字段或方法信息。 - -如果怀疑不是自己的代码,可以使用 **jad** 命令直接反编译 class。 - -![jad](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191106012005747.png) - -`jad` 命令还提供了一些其他参数: - -```shell -# 反编译只显示源码 -jad --source-only com.Arthas -# 反编译某个类的某个方法 -jad --source-only com.Arthas mysql -``` - -## 4.6 查看字段信息 - -使用 **sc -d -f ** 命令查看类的字段信息。 - -```shell -[arthas@20252]$ sc -d -f com.Arthas -sc -d -f com.Arthas - class-info com.Arthas - code-source /C:/Users/Niu/Desktop/arthas/target/classes/ - name com.Arthas - isInterface false - isAnnotation false - isEnum false - isAnonymousClass false - isArray false - isLocalClass false - isMemberClass false - isPrimitive false - isSynthetic false - simple-name Arthas - modifier public - annotation - interfaces - super-class +-java.lang.Object - class-loader +-sun.misc.Launcher$AppClassLoader@18b4aac2 - +-sun.misc.Launcher$ExtClassLoader@2ef1e4fa - classLoaderHash 18b4aac2 - fields modifierfinal,private,static - type org.slf4j.Logger - name log - value Logger[com.Arthas] - - modifierprivate,static - type java.util.HashSet - name hashSet - value [count1, count2] - - modifierprivate,static - type java.util.concurrent.ExecutorService - name executorService - value java.util.concurrent.ThreadPoolExecutor@71c03156[Ru - nning, pool size = 1, active threads = 1, queued ta - sks = 0, completed tasks = 0] - - -Affect(row-cnt:1) cost in 9 ms. -``` - -## 4.7 查看方法信息 - -使用 **sm** 命令查看类的方法信息。 - -```shell -[arthas@22180]$ sm com.Arthas -com.Arthas ()V -com.Arthas start()V -com.Arthas thread()V -com.Arthas deadThread()V -com.Arthas lambda$cpuHigh$1()V -com.Arthas cpuHigh()V -com.Arthas lambda$thread$3()V -com.Arthas addHashSetThread()V -com.Arthas cpuNormal()V -com.Arthas cpu()V -com.Arthas lambda$addHashSetThread$0()V -com.Arthas lambda$deadThread$4(Ljava/lang/Object;Ljava/lang/Object;)V -com.Arthas lambda$deadThread$5(Ljava/lang/Object;Ljava/lang/Object;)V -com.Arthas lambda$cpuNormal$2()V -Affect(row-cnt:16) cost in 6 ms. -``` - -## 4.8 对变量的值很是好奇 - -使用 **ognl** 命令,ognl 表达式可以轻松操作想要的信息。 - -代码还是上面的示例代码,我们查看变量 `hashSet` 中的数据: - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571196786678.png) - -查看静态变量 `hashSet` 信息。 - -```shell -[arthas@19856]$ ognl '@com.Arthas@hashSet' -@HashSet[ - @String[count1], - @String[count2], - @String[count29], - @String[count28], - @String[count0], - @String[count27], - @String[count5], - @String[count26], - @String[count6], - @String[count25], - @String[count3], - @String[count24], -``` - -查看静态变量 hashSet 大小。 - -```shell -[arthas@19856]$ ognl '@com.Arthas@hashSet.size()' - @Integer[57] -``` - -甚至可以进行操作。 - -```shell -[arthas@19856]$ ognl '@com.Arthas@hashSet.add("test")' - @Boolean[true] -[arthas@19856]$ -# 查看添加的字符 -[arthas@19856]$ ognl '@com.Arthas@hashSet' | grep test - @String[test], -[arthas@19856]$ -``` - -`ognl` 可以做很多事情,可以参考 [ognl 表达式特殊用法( https://github.com/alibaba/arthas/issues/71 )](https://github.com/alibaba/arthas/issues/71)。 - -## 4.9 程序有没有问题 - -### 4.9.1 运行较慢、耗时较长 - -使用 **trace** 命令可以跟踪统计方法耗时 - -这次换一个模拟代码。一个最基础的 Springboot 项目(当然,不想 Springboot 的话,你也可以直接在 UserController 里 main 方法启动)控制层 `getUser` 方法调用了 `userService.get(uid);`,这个方法中分别进行`check`、`service`、`redis`、`mysql`操作。 - -```java -@RestController -@Slf4j -public class UserController { - - @Autowired - private UserServiceImpl userService; - - @GetMapping(value = "/user") - public HashMap getUser(Integer uid) throws Exception { - // 模拟用户查询 - userService.get(uid); - HashMap hashMap = new HashMap<>(); - hashMap.put("uid", uid); - hashMap.put("name", "name" + uid); - return hashMap; - } -} -``` - -模拟代码 Service: - -```java -@Service -@Slf4j -public class UserServiceImpl { - - public void get(Integer uid) throws Exception { - check(uid); - service(uid); - redis(uid); - mysql(uid); - } - - public void service(Integer uid) throws Exception { - int count = 0; - for (int i = 0; i < 10; i++) { - count += i; - } - log.info("service end {}", count); - } - - public void redis(Integer uid) throws Exception { - int count = 0; - for (int i = 0; i < 10000; i++) { - count += i; - } - log.info("redis end {}", count); - } - - public void mysql(Integer uid) throws Exception { - long count = 0; - for (int i = 0; i < 10000000; i++) { - count += i; - } - log.info("mysql end {}", count); - } - - public boolean check(Integer uid) throws Exception { - if (uid == null || uid < 0) { - log.error("uid不正确,uid:{}", uid); - throw new Exception("uid不正确"); - } - return true; - } -} - -``` - -运行 Springboot 之后,使用 **trace== ** 命令开始检测耗时情况。 - -```shell -[arthas@6592]$ trace com.UserController getUser -``` - -访问接口 `/getUser` ,可以看到耗时信息,看到 `com.UserServiceImpl:get() `方法耗时较高。 -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571208153793.png) - -继续跟踪耗时高的方法,然后再次访问。 - -```shell -[arthas@6592]$ trace com.UserServiceImpl get -``` - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571208245597.png) - -很清楚的看到是 `com.UserServiceImpl `的 `mysql `方法耗时是最高的。 - -```java -Affect(class-cnt:1 , method-cnt:1) cost in 31 ms. -`---ts=2019-10-16 14:40:10;thread_name=http-nio-8080-exec-8;id=1f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@23a918c7 - `---[6.792201ms] com.UserServiceImpl:get() - +---[0.008ms] com.UserServiceImpl:check() #17 - +---[0.076ms] com.UserServiceImpl:service() #18 - +---[0.1089ms] com.UserServiceImpl:redis() #19 - `---[6.528899ms] com.UserServiceImpl:mysql() #20 -``` - -### 4.9.2 统计方法耗时 - -使用 **monitor** 命令监控统计方法的执行情况。 - -每5秒统计一次 `com.UserServiceImpl` 类的 `get` 方法执行情况。 - -```shell -monitor -c 5 com.UserServiceImpl get -``` - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571210158018.png) - -## 4.10 想观察方法信息 - -下面的示例用到了文章的前两个模拟代码。 - -### 4.10.1 观察方法的入参出参信息 - -使用 **watch** 命令轻松查看输入输出参数以及异常等信息。 - -```shell - USAGE: - watch [-b] [-e] [-x ] [-f] [-h] [-n ] [-E] [-M ] [-s] class-pattern method-pattern express [condition-express] - - SUMMARY: - Display the input/output parameter, return object, and thrown exception of specified method invocation - The express may be one of the following expression (evaluated dynamically): - target : the object - clazz : the object's class - method : the constructor or method - params : the parameters array of method - params[0..n] : the element of parameters array - returnObj : the returned object of method - throwExp : the throw exception of method - isReturn : the method ended by return - isThrow : the method ended by throwing exception - #cost : the execution time in ms of method invocation - Examples: - watch -b org.apache.commons.lang.StringUtils isBlank params - watch -f org.apache.commons.lang.StringUtils isBlank returnObj - watch org.apache.commons.lang.StringUtils isBlank '{params, target, returnObj}' -x 2 - watch -bf *StringUtils isBlank params - watch *StringUtils isBlank params[0] - watch *StringUtils isBlank params[0] params[0].length==1 - watch *StringUtils isBlank params '#cost>100' - watch -E -b org\.apache\.commons\.lang\.StringUtils isBlank params[0] - - WIKI: - https://alibaba.github.io/arthas/watch -``` - -常用操作: - -```shell -# 查看入参和出参 -$ watch com.Arthas addHashSet '{params[0],returnObj}' -# 查看入参和出参大小 -$ watch com.Arthas addHashSet '{params[0],returnObj.size}' -# 查看入参和出参中是否包含 'count10' -$ watch com.Arthas addHashSet '{params[0],returnObj.contains("count10")}' -# 查看入参和出参,出参 toString -$ watch com.Arthas addHashSet '{params[0],returnObj.toString()}' -``` - -查看入参出参。 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571196483469.png) - -查看返回的异常信息。 - -### 4.10.2 观察方法的调用路径 - -使用 **stack**命令查看方法的调用信息。 - -```shell -# 观察 类com.UserServiceImpl的 mysql 方法调用路径 -stack com.UserServiceImpl mysql -``` - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571210706602.png) - -### 4.10.3 方法调用时空隧道 - -使用 **tt** 命令记录方法执行的详细情况。 - -> **tt** 命令方法执行数据的时空隧道,记录下指定方法每次调用的入参和返回信息,并能对这些不同的时间下调用进行观测 。 - -常用操作: - -开始记录方法调用信息:tt -t com.UserServiceImpl check - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571212007249.png) - -可以看到记录中 INDEX=1001 的记录的 IS-EXP = true ,说明这次调用出现异常。 - -查看记录的方法调用信息: tt -l - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571212080071.png) - -查看调用记录的详细信息(-i 指定 INDEX): tt -i 1001 - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571212151064.png) - -可以看到 INDEX=1001 的记录的异常信息。 - - 重新发起调用,使用指定记录,使用 -p 重新调用。 - -```java -tt -i 1001 -p -``` - -![](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571212227058.png) - - - -## 4.5. 火焰图分析 - -最近 Arthas 性能分析工具上线了**火焰图**分析功能,Arthas 使用 **async-profiler** 生成 CPU/内存火焰图进行性能分析,弥补了之前内存分析的不足。在 Arthas 上使用还是比较方便的。 - -**`profiler`** 命令支持生成应用热点的火焰图。本质上是通过不断的采样,然后把收集到的采样结果生成火焰图。 - -**`profiler`** 命令基本运行结构是 **`profiler action [actionArg]`** - -### **4.5.1.使用案例** - -**开启 prifilter** - -默认情况下,生成的是cpu的火焰图,即event为`cpu`。可以用`--event`参数来指定,使用 start 命令开始捕获信息。 - -```shell -$ profiler start -Started [cpu] profiling -``` - -获取已采集的sample的数量 - -```shell -$ profiler getSamples -23 -``` - -查看 profiler状态,可以查看当前 profiler 在采样哪种 `event `和进行的采样时间。 - -$ profiler status - -[cpu] profiling is running for 4 seconds - -**停止profiler** - -生成svg格式火焰图 - -```shell -$ profiler stop -profiler output file: /tmp/demo/arthas-output/20191125-135546.svg -OK -``` - -默认情况下,生成的结果保存到应用的`工作目录`下的`arthas-output`目录。可以通过 `--file`参数来指定输出结果路径。 - -比如: - -```shell -$ profiler stop --file /tmp/output.svg -``` - -**HTML 格式输出** - -默认情况下,结果文件是`svg`格式,如果想生成`html`格式,可以用`--format`参数指定:$ profiler stop --format html - -**查看 profilter** - -默认情况下,arthas使用3658端口,则可以打开: http://localhost:3658/arthas-output/ 查看到`arthas-output`目录下面的profiler结果: - -![img](https://alibaba.github.io/arthas/_images/arthas-output.jpg) - -点击可以查看具体的结果:**火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息** - -![img](https://alibaba.github.io/arthas/_images/arthas-output-svg.jpg) - -**profilter 自持多种分析方式,**常见的有 event: cpu|alloc|lock|cache-misses etc. 比如要分析内存使用情况。 - -$ profiler start --event alloc - -### 4.5.2. 复杂命令 - -比如开始采样: - -```shell -profiler execute 'start' -``` - -停止采样,并保存到指定文件里: - -```shell -profiler execute 'stop,file=/tmp/result.svg' -``` - - - -文中代码已经上传到 [Github](https://github.com/niumoo/lab-notes/tree/master/web-arthas)。 - -https://github.com/niumoo/lab-notes/tree/master/web-arthas - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-async-profiler.md b/docs/tool/tool-async-profiler.md deleted file mode 100644 index 46446a0..0000000 --- a/docs/tool/tool-async-profiler.md +++ /dev/null @@ -1,300 +0,0 @@ ---- -title: 超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下 -toc_number: false -date: 2019-12-09 08:55:08 -url: async-profiler -tags: - - 性能分析 - - 火焰图 - - Async-profiler -categories: - - 生产工具 -typora-root-url: ..\ ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![火焰图](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191209082448987.png) - -如果你经常遇到 Java 线上性能问题束手无策,看着线上服务 CPU 飙升一筹莫展,发现内存不断泄露满脸茫然。别慌,这里有一款低开销、自带**火焰图**、让你大呼好用的 Java 性能分析工具 - **async-profiler**。 - -最近 Arthas 性能分析工具上线了**火焰图**分析功能,Arthas 使用 **async-profiler** 生成 CPU/内存火焰图进行性能分析,弥补了之前内存分析的不足。在 Arthas 上使用还是比较方便的,使用方式可以看官方文档。这篇文章介绍 **async-profiler** 相关内容。 - -Arthas 火焰图官方文档:[https://alibaba.github.io/arthas/profiler.html](https://alibaba.github.io/arthas/profiler.html) - -如果你想了解更多 Arthas 信息,可以参考之前文章:[Arthas - Java 线上问题定位处理的终极利器](http://mp.weixin.qq.com/s?__biz=MzI1MDIxNjQ1OQ==&mid=2247483907&idx=1&sn=653ecea3697873ad1f5c4f567034a1c3&chksm=e984eb67def362715c0a01bc08e1491f7e8f892e92826406484174da264cb06bbc1c05cf30f6&scene=21#wechat_redirect) - - - -## async-profiler 介绍 - -async-profiler 是一款开源的 Java **性能分析工具**,原理是基于 HotSpot 的 API,以**微乎其微的性能开销**收集程序运行中的堆栈信息、内存分配等信息进行分析。 - -使用 async-profiler 可以做下面几个方面的分析。 - -- **CPU cycles** -- Hardware and Software performance counters like cache misses, branch misses, page faults, context switches etc. -- **Allocations in Java Heap** -- Contented lock attempts, including both Java object monitors and ReentrantLocks - -我们常用的是 CPU 性能分析和 Heap 内存分配分析。在进行 CPU 性能分析时,仅需要**非常低的性能开销**就可以进行分析,这也是这个工具的优点之一。 - -在进行 Heap 分配分析时,async-profiler 工具会收集内存分配信息,而不是去检测占用 CPU 的代码。async-profiler 不使用侵入性的技术,例如字节码检测工具或者探针检测等,这也说明 async-profiler 的内存分配分析像 CPU 性能分析一样,不会产生太大的性能开销,同时也不用写出**庞大的堆栈文件**再去进行进一步处理,。 - -async-profile 目前支持 Linux 和 macOS 平台(macOS 下只能分析用户空间的代码)。 - -- **Linux** / x64 / x86 / ARM / AArch64 -- **macOS** / x64 - - -async-profiler 工具在采样后可以生成采样结果的日志报告,也可以生成 SVG 格式的**火焰图**,在之前生成**火焰图**要使用 [FlameGraph](https://github.com/brendangregg/FlameGraph) 工具。现在已经不需要了,从 1.2 版本开始,就已经内置了开箱即用的 SVG 文件生成功能。 - -其他信息可以看官方文档:[https://github.com/jvm-profiling-tools/async-profiler](https://github.com/jvm-profiling-tools/async-profiler) - -## async-profiler 安装 - -下载 async-profiler 工具可以在官方的 Github 上直接下载编译好的文件,如果你就是想体验手动挡的感觉,也可以克隆项目,手动编译一下,不得不说这个工具十分的易用,我在手动编译的过程十分顺滑,没有出现任何问题。 - -如果你想下载编译好的,可以到这里下载。 - -[https://github.com/jvm-profiling-tools/async-profiler/releases](https://github.com/jvm-profiling-tools/async-profiler/releases) - -如果想体验手动挡的感觉,可以克隆整个项目,进项项目编译。 - -手动编译的环境要求。 - -- JDK -- GCC - -下面是手动安装的操作命令。 - -```shell -git clone https://github.com/jvm-profiling-tools/async-profiler -cd async-profiler -make -``` - -执行 make 命令编译后会在项目的目录下生成一个 build 文件夹,里面存放着编译的结果。下面是我手动编译的过程输出。 - - -```shell -➜ develop git clone https://github.com/jvm-profiling-tools/async-profiler -Cloning into 'async-profiler'... -remote: Enumerating objects: 69, done. -remote: Counting objects: 100% (69/69), done. -remote: Compressing objects: 100% (54/54), done. -remote: Total 1805 (delta 34), reused 32 (delta 15), pack-reused 1736 -Receiving objects: 100% (1805/1805), 590.78 KiB | 23.00 KiB/s, done. -Resolving deltas: 100% (1288/1288), done. -➜ develop cd async-profiler -➜ async-profiler git:(master) make -mkdir -p build -g++ -O2 -D_XOPEN_SOURCE -D_DARWIN_C_SOURCE -DPROFILER_VERSION=\"1.6\" -I/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/include -I/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/include/darwin -fPIC -shared -o build/libasyncProfiler.so src/*.cpp -ldl -lpthread -gcc -O2 -DJATTACH_VERSION=\"1.5\" -o build/jattach src/jattach/jattach.c -mkdir -p build/classes -/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/javac -source 6 -target 6 -d build/classes src/java/one/profiler/AsyncProfiler.java src/java/one/profiler/AsyncProfilerMXBean.java src/java/one/profiler/Counter.java src/java/one/profiler/Events.java -警告: [options] 未与 -source 1.6 一起设置引导类路径 -1 个警告 -/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/jar cvf build/async-profiler.jar -C build/classes . -已添加清单 -正在添加: one/(输入 = 0) (输出 = 0)(存储了 0%) -正在添加: one/profiler/(输入 = 0) (输出 = 0)(存储了 0%) -正在添加: one/profiler/AsyncProfiler.class(输入 = 1885) (输出 = 908)(压缩了 51%) -正在添加: one/profiler/Events.class(输入 = 405) (输出 = 286)(压缩了 29%) -正在添加: one/profiler/Counter.class(输入 = 845) (输出 = 473)(压缩了 44%) -正在添加: one/profiler/AsyncProfilerMXBean.class(输入 = 631) (输出 = 344)(压缩了 45%) -rm -rf build/classes -➜ async-profiler git:(master) -``` - -## async-profiler 使用 - -运行项目里的 profiler.sh 可以看到 async-profiler 的使用帮助文档。 - -```shell -➜ async-profiler git:(master) ./profiler.sh -Usage: ./profiler.sh [action] [options] -Actions: - start start profiling and return immediately - resume resume profiling without resetting collected data - stop stop profiling - status print profiling status - list list profiling events supported by the target JVM - collect collect profile for the specified period of time - and then stop (default action) -Options: - -e event profiling event: cpu|alloc|lock|cache-misses etc. - -d duration run profiling for seconds - -f filename dump output to - -i interval sampling interval in nanoseconds - -j jstackdepth maximum Java stack depth - -b bufsize frame buffer size - -t profile different threads separately - -s simple class names instead of FQN - -g print method signatures - -a annotate Java method names - -o fmt output format: summary|traces|flat|collapsed|svg|tree|jfr - -v, --version display version string - - --title string SVG title - --width px SVG width - --height px SVG frame height - --minwidth px skip frames smaller than px - --reverse generate stack-reversed FlameGraph / Call tree - - --all-kernel only include kernel-mode events - --all-user only include user-mode events - --sync-walk use synchronous JVMTI stack walker (dangerous!) - - is a numeric process ID of the target JVM - or 'jps' keyword to find running JVM automatically - -Example: ./profiler.sh -d 30 -f profile.svg 3456 - ./profiler.sh start -i 999000 jps - ./profiler.sh stop -o summary,flat jps -``` - -可以看到使用的方式是:Usage: ./profiler.sh [action] [options] ,也就是 **命令+操作+参数+PID**。 - -常用的使用的几个步骤: - -1. 查看 java 进程的 PID(可以使用 jps )。 -2. 使用 ./profiler.sh start 开始采样。 -3. 使用 ./profiler.sh status 查看已经采样的时间。 -4. 使用 ./profiler.sh stop 停止采样,输出结果。 - -这种方式使用起来多费劲啊,而且最后输出的是文本结果,看起来更是费劲,为了不那么费劲,可以使用帮助里给的采样后生成 SVG 文件例子。 - -```shell -./profiler.sh -d 30 -f profile.svg 3456 -``` - -这个命令的意思是,对 PID 为 3456 的 java 进程采样 30 秒,然后生成 profile.svg 结果文件。 - -默认情况下是分析 CPU 性能,如果要进行其他分析,可以使用 -e 参数。 - -```shell --e event profiling event: cpu|alloc|lock|cache-misses etc. -``` - -可以看到支持的分析事件有 CPU、Alloc、Lock、Cache-misses 。 - -## async-profiler 案例 - -上面说完了 async-profiler 工具的作用和使用方式,既然能进行 CPU 性能分析和 Heap 内存分配分析,那么我们就写几个不一般的方法分析试试看。看看是不是有像上面介绍的那么好用。 - -### Java 案例编码 - -很简单的几个方法,hotmethod 方法写了几个常见操作,三个方法中很明显 hotmethod3 方法里的生成 UUID 和 replace(需要正则匹配)操作消耗的 CPU 性能会较多。allocate 方法里因为要不断的创建长度为 6万的数组,消耗的内存空间一定是最多的。 - -```java -import java.util.ArrayList; -import java.util.Random; -import java.util.UUID; - -/** - *

- * 模拟热点代码 - * - * @Author niujinpeng - */ -public class HotCode { - - private static volatile int value; - - private static Object array; - - public static void main(String[] args) { - while (true) { - hotmethod1(); - hotmethod2(); - hotmethod3(); - allocate(); - } - } - - /** - * 生成 6万长度的数组 - */ - private static void allocate() { - array = new int[6 * 1000]; - array = new Integer[6 * 1000]; - } - - /** - * 生成一个UUID - */ - private static void hotmethod3() { - ArrayList list = new ArrayList<>(); - UUID uuid = UUID.randomUUID(); - String str = uuid.toString().replace("-", ""); - list.add(str); - } - - /** - * 数字累加 - */ - private static void hotmethod2() { - value++; - } - - /** - * 生成一个随机数 - */ - private static void hotmethod1() { - Random random = new Random(); - int anInt = random.nextInt(); - } -} -``` - -### CPU 性能分析 - -运行上面的程序,然后使用 JPS 命令查看 PID 信息。 - -```shell -➜ develop jps -2800 Jps -2449 HotCode -2450 Launcher -805 RemoteMavenServer36 -470 NutstoreGUI -699 -➜ develop -``` - -上面运行的类名是 HotCode,可以看到对应的 PID 是 2449。 - -使用 `./profiler.sh -d 20 -f 2449.svg 2449` 命令对 2449 号进程采样20秒,然后得到生成的 2449.svg 文件,然后我们使用浏览器打开这个文件,可以看到 CPU 的使用**火焰图**。 - -![CPU 使用火焰图](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191208225253486.png) - -关于火焰图怎么看,一言以蔽之:**火焰图里,横条越长,代表使用的越多,从下到上是调用堆栈信息**。在这个图里可以看到 main 方法上面的调用中 hotmethod3 方法的 CPU 使用是最多的,点击这个方法。还可能看到更详细的信息。 - -![hotmethod3 CPU 火焰图](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191208225613748.png) - -可以看到 replace 方法占用的 CPU 最多,也是程序中性能问题所在,是需要注意的地方。 - -### Heap 内存分析 - -还是上面运行的程序,进程 PID 还是 2449,这次使用 -e 参数分析内存使用情况。 - -命令:`./profiler.sh -d 20 -e alloc -f 2449-alloc.svg 2449` - -命令的意思是收集进程号是 2449 的进程的内存信息 20 秒,然后输出为 2449-alloc.svg 文件。20秒后得到 svg 文件使用浏览器打开,可以看到内存分配情况。 - -![内存分配火焰图](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/image-20191208230100288.png) - -依旧是横条越长,代表使用的越多,从下到上是调用堆栈信息。从图里可以看出来 main 方法调用的 allocate 方法使用的内存最多,这个方法里的 Integer 类型数组占用的内存又最多,为 71%。 - -文中测试代码已经上传到 Github:[https://github.com/niumoo/lab-notes](https://github.com/niumoo/lab-notes) - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-curl.md b/docs/tool/tool-curl.md deleted file mode 100644 index 5f15fb3..0000000 --- a/docs/tool/tool-curl.md +++ /dev/null @@ -1,233 +0,0 @@ ---- -title: 可以Postman,也可以cURL.进来领略下cURL的独门绝技 -date: 2020-06-01 08:00:00 -url: tool/curl -categories: - - 生产工具 -tags: - - curl - - Linux ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![curl logo](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/curl-logo.svg) - - - -[**cURL**](https://curl.haxx.se/) 是一个开源免费项目,主要是命令行工具 **cURL** 和 libcurl,**cURL** 可以处理任何网络传输协议,但是不涉及任何具体的**数据处理**。 - -**cURL** 支持的通信协议非常丰富,如 DICT,FILE,FTP,FTPS,GOPHER,HTTP,HTTPS,IMAP,IMAPS,LDAP,LDAPS,MQTT,POP3,POP3S,RTMP, RTMPS,RTSP,SCP,SFTP,SMB,SMBS,SMTP,SMTPS,TELNET 以及 TFTP。查看 cURL 源代码可以访问官方 [Github](https://github.com/curl/curl)。 - -如果安装 **cURL** 呢? - -ubuntu / Debian. - -```shell -sudo apt install curl -``` - -CentOS / Fedora. - -```shell -sudo yum install curl -``` - -Windows. - -如果你已经安装了 Git,那么 Git Bash 自带 **cURL** . 如果作为开发者你 git 都没有,那么只能官方[手动下载](https://curl.haxx.se/download.html)。 - -### 1. 请求源码 - -直接 curl 。 - -```shell -$ curl http://wttr.in/ -``` - -上面请求的示例网址是一个天气网站,很有意思,会根据你的请求 ip 信息返回你所在位置的天气情况。 - -![curl wttr.in](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200530175034329.png) - -写这篇文字时我所在的上海正在下雨,窗外飘雨无休无止。 - -### 2. 文件下载 - -使用 `-o` 保存文件,类似于 wget 命令,比如下载 README 文本保存为 readme.txt 文件。如果你需要自定义文件名,可以使用 `-O`自定使用 url 中的文件名。 - -```shell -$ curl -o readme.txt https://mirrors.nju.edu.cn/kali/README - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 159 100 159 0 0 1939 0 --:--:-- --:--:-- --:--:-- 1939 -``` - -下载文件会显示下载状态,如数据量大小、传输速度、剩余时间等。可以使用 `-s` 参数禁用进度表。 - -```shell -$ curl -o readme.txt https://mirrors.nju.edu.cn/kali/README - % Total % Received % Xferd Average Speed Time Time Time Current - Dload Upload Total Spent Left Speed -100 159 100 159 0 0 1939 0 --:--:-- --:--:-- --:--:-- 1939 -$ -$ curl -o readme.txt https://mirrors.nju.edu.cn/kali/README -s -``` -也可以使用 `--process-bar` 参数让进度表显示为进度条。 -```shell -$ curl -o readme.txt https://mirrors.nju.edu.cn/kali/README --progress-bar -########################################################################################## 100.0% -``` - -**cURL** 作为强大的代名词,断点续传自然手到擒来,使用 `-C -` 参数即可。下面是断点续传下载 ubuntu20.04 镜像的例子。 - -```shell -$ curl -O https://mirrors.nju.edu.cn/ubuntu-releases/20.04/ubuntu-20.04-desktop-amd64.iso --progress-bar -## 1.7% -^C -$ curl -C - -O https://mirrors.nju.edu.cn/ubuntu-releases/20.04/ubuntu-20.04-desktop-amd64.iso --progress-bar -### 2.4% -^C -$ curl -C - -O https://mirrors.nju.edu.cn/ubuntu-releases/20.04/ubuntu-20.04-desktop-amd64.iso --progress-bar -### 2.7% -^C -$ -``` - -什么?下载时不想占用太多网速?使用 `--limit-rate` 限个速吧。 - -```shell -curl -C - -O https://mirrors.nju.edu.cn/ubuntu-releases/20.04/ubuntu-20.04-desktop-amd64.iso --limit-rate 100k -``` - -什么?你又要从 FTP 服务器下载文件了?不慌。 - -```shell -curl -u user:password -O ftp://ftp_server/path/to/file/ -``` - -### 3. Response Headers - -使用 `-i` 参数显示 Response Headers 信息。使用 `-I` 可以只显示 Response Headers 信息。 - -```shell -$ curl -I http://wttr.in -HTTP/1.1 200 OK -Server: nginx/1.10.3 -Date: Sat, 30 May 2020 09:57:03 GMT -Content-Type: text/plain; charset=utf-8 -Content-Length: 8678 -Connection: keep-alive -Access-Control-Allow-Origin: * -``` - -### 4. 请求方式(GET/POST/...) - -使用 `-X` 轻松更改请求方式。 - -```shell -$ curl -X GET http://wttr.in -$ curl -X POST http://wttr.in -$ curl -X PUT http://wttr.in -... -``` - -### 5. 请求参数 - -以传入参数 `name` 值为 `未读代码` 为例。 - -Get 方式参数直接url拼接参数。 - -```shell -$ curl -X GET http://wttr.in?name=未读代码 -``` - -Post 方式使用 `--data` 设置参数。 - -```shell -$ curl -X POST --data "name=未读代码" http://wttr.in -``` - -请求时也可以自定义 **header** 参数,使用 `--harder` 添加。 - -```shell -$ curl --header "Content-Type:application/json" http://wttr.in -``` - -### 6. 文件上传 - -**cURL** 的强大远不止此,表单提交,上传文件内容也不在话下,只需要使用 `-F` 或者 `-D`参数,`-F` 会自动加上请求头 `Content-Type: multipart/form-data` ,而 `-D` 则是 `Content-Type : application/x-www-form-urlencoded`. - -比如上传一个 protrait.jpg 图片。 - -```shell -$ curl -F profile=@portrait.jpg https://example.com/upload -``` - -提交一个具有 name 和 age 参数的 form 表单。 - -```shell -curl -F name=Darcy -F age=18 https://example.com/upload -``` - -参数对应的内容也可以从文件中读取。 - -```shell -curl -F "content=<达西的身世.txt" https://example.com/upload -``` - -上传时同时指定内容类型。 - -```shell -curl -F "content=<达西的身世.txt;type=text/html" https://example.com/upload -``` - -上传文件的和其他参数一起。 - -```shell -curl -F 'file=@"localfile";filename="nameinpost"' example.com/upload -``` - -### 7. 网址通配 - -**cURL** 可以实现多个网址的匹配,你可以使用 `{}` 结合逗号分割来标识使用 url 中的某一段,也可以使用 `[]` 来表示范围参数。 - -```shell -# 请求 www.baidu.com 和 pan.baidu.com 和 fanyi.baidu.com -$ curl http://{www,pan,fanyi}.baidu.com -# 虚构网址1-10开头的baidu.com,然后请求 -$ curl http://[1-10].baidu.com -# 虚构网址a-z开头的baidu.com,然后请求 -$ curl http://[a-z].baidu.com -``` - -这种方式有时候还是很有用处的,比如说你发现了某个网站的 url 规律。 - -### 8. 使用 cookie - -请求时使用 `-c` 参数存储响应的 cookie,使用 `-b` 可以在请求时带上指定 cookie. - -```shell -$ curl -c wdbyte_cookies http://www.wdbyte.com -$ curl -b wdbyte_cookes http://www.wdbyte.com -``` - -### 总结 - -以上就是 **cURL** 的常见用法了,最后告诉你一个小技巧,Chrome、Firefox 等浏览器可以直接拷贝请求为 cURL 语句。保存之后下次请求测试非常方便。 - -![Chrome 复制 cURL 请求](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200531151839505.png) - - -**参考资料** - -1. https://curl.haxx.se/docs/manpage.html - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) diff --git a/docs/tool/tool-idea-skill.md b/docs/tool/tool-idea-skill.md deleted file mode 100644 index ce5bbc3..0000000 --- a/docs/tool/tool-idea-skill.md +++ /dev/null @@ -1,176 +0,0 @@ ---- -title: 抛弃Eclipse,投入IDEA 的独孤求败江湖 -# toc: false -date: 2019-10-23 01:01:01 -url: develop/idea-skill -tags: - - Idea - - 开发工具 -categories: - - 生产工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -> “工欲善其事,必先利其器” -> 出处:孔子《论语》 - -两年了,这是我的 IDEA **实用技巧**总结,从前我是一个 Eclipse 忠实用户,直到某天我用上了 **IntelliJ IDEA** ,Eclipse 开始在硬盘躺尸....... - -![IDEA 启动](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571750577728.png) - -**IDEA** 是一个非常好用的工具,它的语法十分智能,当你写了一段不怎么聪明(很傻)的代码时候,它会告诉你有更优的写法;当你掌握了常用的快捷键后,开发效率提升 N 倍;当你熟练使用时候,编码速度与编码质量都有质的飞跃。 - -那么说了那么多,到底在哪里才能买到呢?哦不,到底怎么使用呢? - -## 1. 窗口切换 - 凌波微步 - -> 「凌波微步」乃是一门极上乘的轻功身法,所以列于卷轴之末,以易经八八六十四卦为基础,使用者按特定顺序踏着卦象方位行进,从第一步到最后一步正好行走一个大圈。此步法精妙异常。 -> 出处:金庸《天龙八部》。 - -IDEA 中的 “凌波微步” 恰好就是这么一圈常用窗口,均匀分布在编辑窗口周围,且配以方便的快捷键,切换起来十分的迅速。 - -![IDEA 窗口](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571756048466.png) - -在实际的开发过程中,窗口切换的操作是很频繁的,下面的几个快捷键必不可少。 - -快捷键: - -- **ALT +1**显示/隐藏文件窗口⭐。 - -- **ALT + 2**显示/隐藏收藏窗口 。 - -- **ALT + 4** 显示/隐藏运行窗口 。 - -- **ALT + 6**显示/隐藏Todo窗口 。 - -- **ALT + 7**显示/隐藏类结构窗口。 - -## 2. 项目切换 - 来而不往 - ->往而不来,非礼也;来而不往,亦非礼也。 ->出处: 《[礼记](https://baike.baidu.com/item/礼记)·[曲礼](https://baike.baidu.com/item/曲礼)上》 - -![窗口切换](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571756766101.png) - -开发当中很多时候会同时打开多个项目,如果这时候你还傻傻的点击窗口切换窗口,无疑效率低下,点来点去之间影响了敲击键盘的快感。既然同时打开了多个项目窗口,作为窗口邻居都不能自由来往有点不尽人意。 - -快捷键: - -- 上一个项目窗口 **CTRL + ALT + [。** -- 上一个项目窗口 **CTRL + ALT + ]**。 - -## 3. 文件定位 - 来去自如 - -> 身如不系之舟,一任流任坎止;心似既灰之木,何妨刀割香涂?来去自如乎。 -> 出处:战国·庄子《庄子·列御寇》 - -文件定位是**非常常用**的操作。 - -- **CTRL + N** 搜索 Java 类文件⭐。 -- **CTRL + SHIFT + N** 搜索所有文件。 - -- **CTRL + E** 打开最近浏览文件 ,再次 **CTRL + E** 可以只显示更改的文件。 -- **CTRL + SHIFT + E** 打开最近浏览文件 ,再次 **CTRL + SHIFT + E** 可以只显示更改的文件。 -- **SHIFT + 鼠标左键**,关闭文件。 - -## 4. 代码定位 - 百步穿杨 - -> 楚有养由基者,善射;去柳叶百步而射之,百发百中。 -> 出处: 《战国策 · 西周策》 - -用过 Eclipse 的想必都知道 Eclipse 的文本搜索速度是多么缓慢,多个项目搜索时候,看着那缓慢的进度条,仿佛在虚度生命。而 **IDEA** 速度保证让你第一次使用时就为之惊叹。毫秒级的响应速度(自动忽略CPU 百分百,手动滑稽),智能的搜索模式,不要太舒服。 - -那么又说了那么多,到底该怎么用呢? - -**超级常用**快捷键: - -- **CTRL + SHIFT + F** 项目代码全文搜索⭐。 -- **CTRL + SHIFT + ALT + N** 搜索函数,自动模糊匹配,十分强大。 -- **CTRL + W** 万能选中快捷键,爱不释手,多层嵌套时非常好用⭐。 - -## 5. 代码操作 - 出神入化 - -> 我不曾出声,他连忙答应。金圣叹:‘真正出神入化之笔’ -> 出处: 元·王实甫《西厢记》 - -基本的代码操作在日常开发中才是用的最多的,小小的快捷键让我们的开发舒适感不断爬升。低调低调,都是基本操作。 - -- **ALT + ENTER** 万能智能键,强大到无法自拔⭐。 -- **CTRL + ALT + L** 代码格式化⭐。 -- **CTRL + SHIFT + R** 项目全文代码查找与替换⭐。 -- **SHIFT + F6** 重构 - 重命名⭐。 -- **iter + Tab** 生成 增强for 循环⭐。 -- **itar + Tab** 生成 for 循环。 -- **psvm + Tab** 生成 main 方法。 -- **sout + Tab** 生成 System.out.println()。 -- **CTRL + F** 查找文本。 -- **CTRL + R** 替换文本。 -- **CTRL + D** 复制行。 -- **CTRL + X** 剪切行。 -- **CTRL + Y** 删除行。 -- **CTRL + /** 行注释或取消行注释。 -- **CTRL + SHIFT + /** 块注释或取消块注释。 -- **CTRL + SHIFT + ENTER** 自动补上结尾分号,或者补上结尾花括号{}。 -- **CTRL + U** 大小写切换。 - -## 6. 惊世骇俗 - 深藏不露 - -不得不说, **IDEA** 用起来更加舒服的地方就是它总能想你所想,在使用的过程中,经常发现意料之外的惊喜功能。 - -### 6.1 集成插件 - -![IDEA 插件](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571762835305.png) - -一个好用的插件可以让开发事半功倍, IDEA 支持丰富的插件扩展,不管是有助于代码开发类插件,还是帮助分析类插件,还是小工具插件,亦或是主题插件等,都十分丰富,常常你在开发中因为某个问题举步维艰,可能你缺少的只是一款趁手的插件。 - -下面仅仅列举我常用的几款插件,更多插件可以自行探索。 - -1. **A8Translate** ,一款翻译工具,英语不够,工具来凑。 -2. **Eclipse Code Formatter** ,可以结合阿里代码格式化文件进行格式化。 -3. **Lombok** ,Lombok 注解支持。 -4. **Maven Helper** ,Maven 依赖处理。 - -### 6.2 Rest Client - -当你想简单的测试某个接口调用响应情况时候,不必打开你的 postman,**IDEA** 已经默认为你集成了 Rest Client 测试工具。可以 **CTRL+ SHIFT + A** 然后搜索 **restful**。 - -### 6.3 剪切板历史 - -听说你拷贝了一串代码,又拷贝了一串代码,尴尬,之前的拷贝丢失了。莫慌, **IDEA** 想你所想,内置了粘贴板历史。只需要使用快捷键 **CTRL + SHIFT + V** 即可。 - -![剪切板历史](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571788672930.png) - -### 6.4 JSON 格式化 - -当控制台打印了JSON 字符串时,可以右键格式化显示,对于 JSON 响应的接口调用测试时候十分好用。 - -![JSON 格式化](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571762204121.png) - -### 6.5 演示模式 - -按 ALT + V 选择 Enter Presentation Mode 进入演示模式,可以放大编辑窗口,十分适合代码操作演示,如果对于 IDEA 快捷键比较熟练,操作起来依旧行云流水。 - -### 6.6 补全判断 - -对某个对象判断是否为 null,或者对某个字符串判空等,都可以使用 **IDEA 智能语法**。 - -比如: - -![代码](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571763565635.png) - -**content.notnull** 回车可以自动生成下面的代码,类似的操作在 IDEA 中还有很多,不再一一列举。 - -![生成代码](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571763587951.png) - -文中的一些 **IDEA** 使用技巧都是工作中经常使用的,当然 **IDEA** 的功能远不止这些。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-install-tomcat-many-instance.md b/docs/tool/tool-install-tomcat-many-instance.md deleted file mode 100644 index e8a7608..0000000 --- a/docs/tool/tool-install-tomcat-many-instance.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -title: Linux配置Tomcat的单机多实例 -date: 2018-08-10 01:14:35 -url: develop/install-tomcat-many-instance -tags: - - Tomcat -categories: - - Linux ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -![多示例](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/1571963198487.png) - -有时候需要在一个服务器上部署多个Tomcat,通过不同的端口进行区分,比如,反向代理。但是不想简单的通过复制Tomcat来实现,这样既不方便以后的升级也不方便管理,那么这时候就需要配置Tomcat的单机多实例了。 -以下操作运行于Linux下。 - -### Tomcat 下载 -Tomcat的下载可以直接到Tomcat官方网站下载自己需要的版本,博主这里下载的Tomcat8.5.*32* - -```shell -// 解压 -tar -zxvf apache-tomcat-8.5.32.tar.gz -``` - -可以看到解压后目录如下: - -```shell -niu@ubuntu:~/develop/test$ cd apache-tomcat-8.5.32/ -niu@ubuntu:~/develop/test/apache-tomcat-8.5.32$ ll -total 120 -drwxr-x--- 2 niu niu 4096 8月 10 01:35 bin/ -drwx------ 2 niu niu 4096 6月 20 12:53 conf/ -drwxr-x--- 2 niu niu 4096 8月 10 01:35 lib/ --rw-r----- 1 niu niu 57092 6月 20 12:53 LICENSE -drwxr-x--- 2 niu niu 4096 6月 20 12:50 logs/ --rw-r----- 1 niu niu 1723 6月 20 12:53 NOTICE --rw-r----- 1 niu niu 7138 6月 20 12:53 RELEASE-NOTES --rw-r----- 1 niu niu 16246 6月 20 12:53 RUNNING.txt -drwxr-x--- 2 niu niu 4096 8月 10 01:35 temp/ -drwxr-x--- 7 niu niu 4096 6月 20 12:51 webapps/ -drwxr-x--- 2 niu niu 4096 6月 20 12:50 work/ -``` - -### 配置多实例模版 -要实现单Tomcat的多实例启动,首先我们要修改一下当前的Tomcat目录结构具体操作如下。为了方便,我们会先配置一个模版实例,然后在模版实例中编写一个启动停止shell脚本。以后扩展实例只需要拷贝一份修改端口号。 -```shell -// 删除无用文件 -rm LICENSE -rm NOTICE -rm RELEASE-NOTES -rm RUNNING.txt -// 创建WEB实例模版文件夹,以后部署新实例只需要拷贝一份 -mkdir web-template -// 移动实例文件到实例模版文件夹 -mv conf/ ./web-template/ -mv logs/ ./web-template/ -mv tem/ ./web-template/ -mv temp/ ./web-template/ -mv webapps/ ./web-template/ -mv work/ ./web-template/ -``` -在模版文件夹下编写启动停止Tomcat的shell脚本。 -```shell -// 新建sehll脚本 -vim tomcat.sh -``` -输入如下内容: -```shell -RETVAL=$? -# tomcat实例目录 -export CATALINA_BASE="$PWD" -# tomcat安装目录,改成自己的 -export CATALINA_HOME="/home/niu/develop/test/apache-tomcat-8.5.32" -# 可选 -export JVM_OPTIONS="-Xms128m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=512m" -case "$1" in -start) -if [ -f $CATALINA_HOME/bin/startup.sh ];then -echo $"Start Tomcat" -$CATALINA_HOME/bin/startup.sh -fi -;; -stop) -if [ -f $CATALINA_HOME/bin/shutdown.sh ];then -echo $"Stop Tomcat" -$CATALINA_HOME/bin/shutdown.sh -fi -;; -*) -echo $"Usage:$0 {start|stop}" -exit 1 -;; -esac -exit $RETVAL -``` -保存退出,赋予执行权限。 -```shell -chmod +x tomcat.sh -``` -经过上面的操作,现在的Tomcat目录结构如下: -```shell -apache-tomcat-8.5.32 -├── bin -├── lib -└── web-template - ├── conf - ├── logs - ├── temp - ├── webapps - └── work -``` -### 测试实例模版 -实例模版中包含config文件夹,也就是此实例的配置文件,可以修改端口号等信息。我们没有进行修改过,默认也就是`8080`。webapps文件夹中的ROOT目录也就是Tomcat的默认发布目录,我们没有进行修改,里面存放的是Tomcat默认首页信息。 -```shell -// 启动模版实例进行测试,可以看到正常启动的日志 -tomcat.sh start -// 停止则使用stop -tomcat.sh stop -``` -成功启动后,访问IP+8080进行测试。 -![Tomcat首页](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/119d56104ea62415e369bae13f6269b6.png) -看到属性的页面,大功告成。距离多实例只有一步之遥。 - -### 增加一个实例 -增加一个实例,只拷贝一份模版实例。然后修改端口号即可。不然会因为端口占用而无法启动。 -```shell -# 拷贝一份实例 -cp -r web-template/ web-9090 -# 修改端口号为9090 -vim conf/server.xml -# 修改HTTP端口号从8080变为9090,第69行左右 - -# 修改SHUTDOWN端口号从8005变为9005,第22行左右 -ver port="9005" shutdown="SHUTDOWN"> -# 保存,退出,启动 -tomcat.sh start -``` - -此时可以访问IP+端口9090进行访问测试。 -![9090实例访问测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2019/e73313a3dd0634c258ae09e34e8be295.png) - -到这里,多实例已经部署完成,关闭各个Tomcat。退出终端。 - -增加实例只需要拷贝模版实例然后修改端口号。每个实例都有自己单独的配置,可以独立管理启动。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file diff --git a/docs/tool/tool-java-download.md b/docs/tool/tool-java-download.md deleted file mode 100644 index e78f73e..0000000 --- a/docs/tool/tool-java-download.md +++ /dev/null @@ -1,210 +0,0 @@ ---- -title: 撸了个多线程断点续传下载器,我从中学习到了这些知识 -date: 2020-07-27 00:08:10 -url: tool/java-download -tags: - - 下载器 - - download - - TCP - - Java -categories: - - Java 开发 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -感谢看客老爷点进来了,周末闲来无事,想起**同事强哥**的那句话:“你有没有玩过**断点续传**?” 当时转念一想,**断点续传**下载用的确实不少,具体细节嘛,真的没有去思考过啊。这不,思考过后有了这篇文章。感谢强哥,让我有了一篇可以水的文章,下面会用纯 Java 无依赖实现一个简单的**多线程断点续传下载器**。 - -这篇水文章到底有什么内容呢?先简单列举一下,顺便思考几个问题。 - -1. 断点续传的原理。 -2. 重启续传文件时,怎么保证文件的一致性? -3. 同一个文件多线程下载如何实现? -4. **网速带宽固定,为什么多线程下载可以提速?** - -多线程断点续传会用到哪些知识呢?上面已经抛出了几个问题,不放思考一下。下面会针对上面的四个问题一一进行解释,现在大多数的服务都可以在线提供,下载使用的场景越来越少,不过这不妨碍我们对原理的探求。 - -## 断点续传的原理 - -想要了解断点续传是如何实现的,那么肯定是要了解一下 HTTP 协议了。HTTP 协议是互联网上应用最广泛网络传输协议之一,它基于 **TCP/IP** 通信协议来传递数据。所以断点续传的奥秘也就隐藏在这 HTTP 协议中了。 - -我们都知道 HTTP 请求会有一个 **Request header** 和 **Response header** ,就在这请求头和响应头里,有一个和 Range 相关的参数。下面通过百度网盘的 pc 客户端下载链接进行测试。 - -使用 cURL 查看 response header. 如果你想知道更多关于 cURL 的用法,可以看我之前的一篇文章 :[进来领略下cURL的独门绝技](https://mp.weixin.qq.com/s/jK4ctq5VQjw4oPSO0gEp6Q)。 - -```shell -$ curl -I http://wppkg.baidupcs.com/issue/netdisk/yunguanjia/BaiduYunGuanjia_7.0.1.1.exe -HTTP/1.1 200 OK -Server: JSP3/2.0.14 -Date: Sat, 25 Jul 2020 13:41:55 GMT -Content-Type: application/x-msdownload -Content-Length: 65804256 -Connection: keep-alive -ETag: dcd0bfef7d90dbb3de50a26b875143fc -Last-Modified: Tue, 07 Jul 2020 13:19:46 GMT -Expires: Sat, 25 Jul 2020 14:05:19 GMT -Age: 257796 -Accept-Ranges: bytes -Cache-Control: max-age=259200 -Content-Disposition: attachment;filename="BaiduYunGuanjia_7.0.1.1.exe" -x-bs-client-ip: MTgwLjc2LjIyLjU0 -x-bs-file-size: 65804256 -x-bs-request-id: MTAuMTM0LjM0LjU2Ojg2NDM6NDM4MTUzMTE4NTU3ODc5MTIxNzoyMDIwLTA3LTA3IDIyOjAxOjE1 -x-bs-meta-crc32: 3545941535 -Content-MD5: dcd0bfef7d90dbb3de50a26b875143fc -superfile: 2 -Ohc-Response-Time: 1 0 0 0 0 0 -Access-Control-Allow-Origin: * -Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS, HEAD -Ohc-Cache-HIT: bj2pbs54 [2], bjbgpcache54 [4] -``` - -可以看到百度 pc 客户端的 response header 信息有很多,我们只需要重点关注几个。 - -```shell -Content-Length: 65804256 // 请求的文件的大小,单位 byte -Accept-Ranges: bytes // 是否允许指定传输范围,bytes:范围请求的单位是 bytes (字节),none:不支持任何范围请求单位, -Last-Modified: Tue, 07 Jul 2020 13:19:46 GMT // 服务端文件最后修改时间,可以用于校验文件是否更改过 -x-bs-meta-crc32: 3545941535 // crc32,可以用于校验文件是否更改过 -ETag: dcd0bfef7d90dbb3de50a26b875143fc //Etag 标签,可以用于校验文件是否更改过 -``` - -可见并不见得所有下载都支持断点续传,只有在 response header 中有 `Accept-Ranges: bytes ` 字段时才可以断点续传。如果有这个信息,该怎么断点续传呢?其实只需要在 response header 中指定 **Content-Range** 值就可以了。 - -**Content-Range** 使用格式有下面几种。 - -```shell -Content-Range: =-/ // size 为文件总大小,如果不知道可以用 * -Content-Range: =-/* -Content-Range: =- -Content-Range: =*/ -``` - -**举例**: - -单位 bytes,从第 10 个 bytes 开始下载:`Content-Range: bytes=10-`. - -单位 bytes,从第 10 个 bytes 开始下载,下载到第100个 bytes:`Content-Range: bytes=10-100`. - -这就是断点续传实现的原理了,你可以能已经发现了,Content-Range 的 start 和 end 已经让分段下载有了可能。 - -## 怎么保证文件的一致性? - -这里要说的文件完整性有两个方面,一个是**下载阶段**的,一个是**写入阶段**的。 - -因为我们要写的下载器是支持断点续传的,那么在进行续传时,怎么确定文件自从我们上次下载时没有进行过更新呢?其实可以通过 response header 中的几个属性值进行判断。 - -```shell -Last-Modified: Tue, 07 Jul 2020 13:19:46 GMT // 服务端文件最后修改时间,可以用于校验文件是否更改过 -ETag: dcd0bfef7d90dbb3de50a26b875143fc //Etag 标签,可以用于校验文件是否更改过 -x-bs-meta-crc32: 3545941535 // crc32,可以用于校验文件是否更改过 -``` - -`Last-Modified` 和 `ETag` 都可以用来检验文件是否更新过,根据 HTTP 协议的规定,当文件更新时,是会生成新的 `ETag` 值的,它类似于文件的指纹信息,而 `Last-Modified` 只是上次修改时间,有时可能并不能够证明文件内容被修改过。 - -上面是下载阶段的文件一致性校验,那么在写入阶段呢?不管单线程还是多线程,由于要断点续传,在写入时都要在**指定位置**进行字符追加。在 Java 中有没有好的实现方式? - -答案是一定的,使用 `RandomAccessFile` 类即可,`RandomAccessFile` 不同于其他的流操作。它可以在使用时指定读写模式,使用 `seek` 方法随意的移动要操作的文件指针位置。很适合断点续传的写入场景。 - -比如在 test.txt 的位置 0 开始写入字符 abc,在位置 100 开始写入字符 ddd. - -```java -try (RandomAccessFile rw = new RandomAccessFile("test.txt", "rw")){ // rw 为读写模式 - rw.seek(0); // 移动文件内容指针位置 - rw.writeChars("abc"); - rw.seek(100); - rw.writeChars("ddd"); -} -``` - -断点续传的写入就靠它了,在续传时只需要移动文件内容指针到要续传的位置即可。 - -`seek` 方法还有很多妙用,比如使用它你可以**快速定位**到已知的位置,进行**快速检索**;也可以在同一个文件的不同位置进行**并发读写**。 - -## 多线程下载如何实现? - -多线程下载必然要每个线程下载文件中的一部分,然后把每个线程下载到的文件内容组装成一个完整的文件,在这个过程中肯定是一个 byte 都不能出错的,不然你组装起来的文件是肯定运行不起来的。那么怎么实现下载文件的一部分呢?其实在断点续传的部分已经介绍过了,还是 `Content-Range` 参数,只要计算好每个部分要下载的 bytes 范围就可以了。 - -比如:单位 bytes,第二部分从第 10 个 bytes 开始下载,下载到第100个 bytes:`Content-Range: bytes=10-100`. - -## 网速带宽固定,为什么多线程下载可以提速? - -这是一个比较有意思的问题了,最大网速是固定的,运营商给你 100Mbs 的网速,不管你怎么使用,速度最大也就是 100/8=12.5MB/S. 既然瓶颈在这里,为什么多线程下载可以提速呢?其实理论上来说,单线程下载就可以达到最大网速。但是往往事实是网络不是那么通畅,十分拥堵,很难达到理想的最大速度。也就是说**只有在网络不那么通畅的时候,多线程下载才能提速**。否则,单线程即可。不过最大速度永远都是网络带宽。 - -那为什么多线程下载可以提速呢?HTTP 协议在传输时候是基于 TCP 协议传输数据的,为了弄明白这个问题需要了解一下 TCP 协议的**拥塞控制**机制。**拥塞控制** 是TCP 的一个**避免网络拥塞**的算法,它是基于**和性增长/乘性降低**这样的控制方法来控制拥塞的。 - -![TCP 拥塞控制](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/image-20200727081418322.png) - -简单来说就是在 TCP 开始传输数据时,服务端会不断的探测可用带宽。在一个**传输内容段**被成功接收后,会加倍传输两倍段内容,如果再次被成功接收,就继续加倍,直到发生了**丢包**,这是这也被叫做**慢启动**。当达到**慢启动阀值(ssthresh)**时,满启动算法就会转换为线性增长的阶段,每次只增加一个分段,放缓增加速度。我觉得其实慢启动的加倍增速过程并不慢,只是一种叫法。 - -但是当发生了丢包,也就是检测到拥塞时,发送方就会将发送段大小**降低一个乘数**,比如二分之一,慢启动阈值降为超时前**拥塞窗口的一半大小**、拥塞窗口会降为1个MSS,并且**重新回到慢启动**阶段。这时多线程的优势就体现出来了,因为你的多线程会让这个速度减速没有那么猛烈,毕竟这时可能有另一个线程正处在慢启动的在最终加速阶段,这样总体的下载速度就优于单线程了。 - -## 多线程断点续传代码实现 - -基于上面的原理介绍,心里应该有了具体的实现思路了。我们只需要使用多线程,结合 `Content-Range` 参数分段请求文件内容保存到临时文件,下载完毕后使用 `RandomAccessFile ` 把下载的文件合并成一个文件即可。而在需要断点续传时,只需要读取一下当前临时文件大小,然后调整 `Content-Range` ,就可以进行续传下载。 - -代码不多,下面是部分核心代码,完整代码可以直接点开文章最后的 Github 仓库。 - -1. `Content-Range` 请求指定文件的区间内容。 - -```java -URL httpUrl = new URL(url); -HttpURLConnection httpConnection = (HttpURLConnection)httpUrl.openConnection(); -httpConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"); -httpConnection.setRequestProperty("RANGE", "bytes=" + start + "-" + end + "/*"); -InputStream inputStream = httpConnection.getInputStream(); -``` - -2. 获取文件的 ETag. - -```java -Map> headerFields = httpConnection.getHeaderFields(); -List eTagList = headerFields.get("ETag"); -System.out.println(eTagList.get(0)); -``` - -3. 使用 `RandomAccessFile ` 续传写入文件。 - -```java -RandomAccessFile oSavedFile = new RandomAccessFile(httpFileName, "rw"); -oSavedFile.seek(localFileContentLength); // 文件写入开始位置指针移动到已经下载位置 -byte[] buffer = new byte[1024 * 10]; -int len = -1; -while ((len = inputStream.read(buffer)) != -1) { - oSavedFile.write(buffer, 0, len); -} -``` - -断点续传测试,下载一部分之后关闭程序再次启动。 - -![多线程下载测试](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets/2020/down-bit-test-min.gif) - - - -完整代码已经上传到 [github.com/niumoo/down-bit](https://github.com/niumoo/down-bit). - - - -**参考:** - -[1] [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers) - -[2] [Class RandomAccessFile](https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/io/RandomAccessFile.html) - -[3] [RandomAccessFile简介与使用](https://blog.csdn.net/qq_31615049/article/details/88562892) - -[4] [维基百科 - TCP拥塞控制]([https://zh.wikipedia.org/wiki/TCP%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6](https://zh.wikipedia.org/wiki/TCP拥塞控制)) - -[5] [维基百科 - 和性增长/乘性降低]([https://zh.wikipedia.org/wiki/%E5%92%8C%E6%80%A7%E5%A2%9E%E9%95%BF/%E4%B9%98%E6%80%A7%E9%99%8D%E4%BD%8E](https://zh.wikipedia.org/wiki/和性增长/乘性降低)) - - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) diff --git a/docs/tool/tool-jmh.md b/docs/tool/tool-jmh.md deleted file mode 100644 index 3532000..0000000 --- a/docs/tool/tool-jmh.md +++ /dev/null @@ -1,385 +0,0 @@ ---- -title: JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握! -date: 2020-08-25 08:01:01 -url: develop/tool-jmh -tags: - - 性能测试 - - jmh -categories: - - 生产工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -## Java 性能测试难题 - -现在的 JVM 已经越来越为智能,它可以在编译阶段、加载阶段、运行阶段对代码进行优化。比如你写了一段不怎么聪明的代码,到了 JVM 这里,它发现几处可以优化的地方,就顺手帮你优化了一把。这对程序的运行固然美妙,却让开发者不能准确了解程序的运行情况。在需要进行性能测试时,如果不知道 JVM 优化细节,可能会导致你的测试结果差之毫厘,失之千里,同样的,Java 诞生之初就有一次编译、随处运行的口号,JVM 提供了底层支持,也提供了内存管理机制,这些机制都会对我们的性能测试结果造成不可预测的影响。 - -```java -long start = System.currentTimeMillis(); -// .... -long end = System.currentTimeMillis(); -System.out.println(end - start); -``` - -上面可能就是你最常见的性能测试了,这样的测试结果真的准确吗?答案是否定的,它有下面几个问题。 - -1. 时间精度问题,本身获取到的时间戳就是存在**误差**的,它和操作系统有关。 -2. JVM 在运行时会进行**代码预热**,说白了就是**越跑越快**。因为类需要装载、需要准备操作。 -3. JVM 会在各个阶段都有可能对你的代码进行**优化处理**。 -4. **资源回收**的不确定性,可能运行很快,回收很慢。 - -带着这些问题,突然发现进行一次严格的基准测试的难度大大增加。那么如何才能进行一次严格的基准测试呢? - -## JMH 介绍 - -那么如何对 Java 程序进行一次精准的性能测试呢?难道需要掌握很多 JVM 优化细节吗?难道要研究如何避免,并进行正确编码才能进行严格的性能测试吗?显然不是,如果是这样的话,未免过于困难了,好在有一款一款官方的微基准测试工具 - **JMH**. - -**JMH** 的全名是 Java Microbenchmark Harness,它是由 **Java 虚拟机团队**开发的一款用于 Java **微基准测试工具**。用自己开发的工具测试自己开发的另一款工具,以子之矛,攻子之盾果真手到擒来,如臂使指。使用 **JMH** 可以让你方便快速的进行一次严格的代码基准测试,并且有多种测试模式,多种测试维度可供选择;而且使用简单、增加注解便可启动测试。 - -## JMH 使用 - -JMH 的使用首先引入 maven 所需依赖,当前最新版 为 1.23 版本。 - -``` xml - - - org.openjdk.jmh - jmh-core - 1.23 - - - org.openjdk.jmh - jmh-generator-annprocess - 1.23 - provided - -``` - -### 快速测试 - -下面使用注解的方式指定测试参数,通过一个例子展示 JMH 基准测试的具体用法,先看一次运行效果,然后再了解每个注解的具体含义。 - -这个例子是使用 JMH 测试,使用加号拼接字符串和使用 `StringBuilder` 的 `append` 方法拼接字符串时的速度如何,每次拼接1000个数字进行平均速度比较。 - -```java -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -/** - *

- * JMH 基准测试入门 - * - * @author niujinpeng - * @Date 2020/8/21 1:13 - */ -@BenchmarkMode(Mode.AverageTime) -@State(Scope.Thread) -@Fork(1) -@OutputTimeUnit(TimeUnit.MILLISECONDS) -@Warmup(iterations = 3) -@Measurement(iterations = 5) -public class JmhHello { - - String string = ""; - StringBuilder stringBuilder = new StringBuilder(); - - @Benchmark - public String stringAdd() { - for (int i = 0; i < 1000; i++) { - string = string + i; - } - return string; - } - - @Benchmark - public String stringBuilderAppend() { - for (int i = 0; i < 1000; i++) { - stringBuilder.append(i); - } - return stringBuilder.toString(); - } - - public static void main(String[] args) throws RunnerException { - Options opt = new OptionsBuilder() - .include(JmhHello.class.getSimpleName()) - .build(); - new Runner(opt).run(); - } -} - -``` - -代码很简单,不做解释,`stringAdd` 使用加号拼接字符串 1000次,`stringBuilderAppend` 使用 `append` 拼接字符串 1000次。直接运行 main 方法,稍等片刻后可以得到详细的运行输出结果。 - -```log -// 开始测试 stringAdd 方法 -# JMH version: 1.23 -# VM version: JDK 1.8.0_181, Java HotSpot(TM) 64-Bit Server VM, 25.181-b13 -# VM invoker: D:\develop\Java\jdk8_181\jre\bin\java.exe -# VM options: -javaagent:C:\ideaIU-2020.1.3.win\lib\idea_rt.jar=50363:C:\ideaIU-2020.1.3.win\bin -Dfile.encoding=UTF-8 -# Warmup: 3 iterations, 10 s each // 预热运行三次 -# Measurement: 5 iterations, 10 s each // 性能测试5次 -# Timeout: 10 min per iteration // 超时时间10分钟 -# Threads: 1 thread, will synchronize iterations // 线程数量为1 -# Benchmark mode: Average time, time/op // 统计方法调用一次的平均时间 -# Benchmark: net.codingme.jmh.JmhHello.stringAdd // 本次执行的方法 - -# Run progress: 0.00% complete, ETA 00:02:40 -# Fork: 1 of 1 -# Warmup Iteration 1: 95.153 ms/op // 第一次预热,耗时95ms -# Warmup Iteration 2: 108.927 ms/op // 第二次预热,耗时108ms -# Warmup Iteration 3: 167.760 ms/op // 第三次预热,耗时167ms -Iteration 1: 198.897 ms/op // 执行五次耗时度量 -Iteration 2: 243.437 ms/op -Iteration 3: 271.171 ms/op -Iteration 4: 295.636 ms/op -Iteration 5: 327.822 ms/op - - -Result "net.codingme.jmh.JmhHello.stringAdd": - 267.393 ±(99.9%) 189.907 ms/op [Average] - (min, avg, max) = (198.897, 267.393, 327.822), stdev = 49.318 // 执行的最小、平均、最大、误差值 - CI (99.9%): [77.486, 457.299] (assumes normal distribution) - -// 开始测试 stringBuilderAppend 方法 -# Benchmark: net.codingme.jmh.JmhHello.stringBuilderAppend - -# Run progress: 50.00% complete, ETA 00:01:21 -# Fork: 1 of 1 -# Warmup Iteration 1: 1.872 ms/op -# Warmup Iteration 2: 4.491 ms/op -# Warmup Iteration 3: 5.866 ms/op -Iteration 1: 6.936 ms/op -Iteration 2: 8.465 ms/op -Iteration 3: 8.925 ms/op -Iteration 4: 9.766 ms/op -Iteration 5: 10.143 ms/op - - -Result "net.codingme.jmh.JmhHello.stringBuilderAppend": - 8.847 ±(99.9%) 4.844 ms/op [Average] - (min, avg, max) = (6.936, 8.847, 10.143), stdev = 1.258 - CI (99.9%): [4.003, 13.691] (assumes normal distribution) - - -# Run complete. Total time: 00:02:42 - -REMEMBER: The numbers below are just data. To gain reusable insights, you need to follow up on -why the numbers are the way they are. Use profilers (see -prof, -lprof), design factorial -experiments, perform baseline and negative tests that provide experimental control, make sure -the benchmarking environment is safe on JVM/OS/HW level, ask for reviews from the domain experts. -Do not assume the numbers tell you what you want them to tell. -// 测试结果对比 -Benchmark Mode Cnt Score Error Units -JmhHello.stringAdd avgt 5 267.393 ± 189.907 ms/op -JmhHello.stringBuilderAppend avgt 5 8.847 ± 4.844 ms/op - -Process finished with exit code 0 -``` - -上面日志里的 `//` 注释是我手动增加上去的,其实我们只需要看下面的最终结果就可以了,可以看到 `stringAdd` 方法平均耗时 267.393ms,而 `stringBuilderAppend` 方法平均耗时只有 8.847ms,可见 `StringBuilder` 的 `append` 方法进行字符串拼接速度快的多,这也是我们推荐使用` append` 进行字符串拼接的原因。 - -### 注解说明 - -经过上面的示例,想必你也可以快速的使用 JMH 进行基准测试了,不过上面的诸多注解你可能还有疑惑,下面一一介绍。 - -**类上**使用了六个注解。 - -``` -@BenchmarkMode(Mode.AverageTime) -@State(Scope.Thread) -@Fork(1) -@OutputTimeUnit(TimeUnit.MILLISECONDS) -@Warmup(iterations = 3) -@Measurement(iterations = 5) -``` - -**@BenchmarkMode(Mode.AverageTime)** 表示统计平均响应时间,不仅可以用在类上,也可用在**测试方法**上。 - -除此之外还可以取值: - -- Throughput:统计单位时间内可以对方法测试多少次。 -- SampleTime:统计每个响应时间范围内的响应次数,比如 0-1ms,3次;1-2ms,5次。 -- SingleShotTime:跳过预热阶段,直接进行**一次****微基准**测试。 - -**@State(Scope.Thread)**:每个进行基准测试的线程都会独享一个对象示例。 - -除此之外还能取值: - -- Benchmark:多线程共享一个示例。 -- Group:线程组共享一个示例,在测试方法上使用 @Group 设置线程组。 - -**@Fork(1)**:表示开启一个线程进行测试。 - -**OutputTimeUnit(TimeUnit.MILLISECONDS):输出的时间单位,这里写的是毫秒。 - -**@Warmup(iterations = 3)**:微基准测试前进行三次预热执行,也可用在**测试方法**上。 - -**@Measurement(iterations = 5)**:进行 5 次微基准测试,也可用在**测试方法**上。 - - - -在两个测试方法上只使用了一个注解 **@Benchmark**,这个注解表示这个方法是要进行基准测试的方法,它类似于 Junit 中的 **@Test** 注解。上面还提到某些注解还可以用到测试方法上,也就是使用了 **@Benchmark** 的方法之上,如果类上和测试方法同时存在注解,会以**方法上的注解**为准。 - -其实 JMH 也可以把这些参数直接在 main 方法中指定,这时 main 方法中指定的级别最高。 - -```java -public static void main(String[] args) throws RunnerException { - Options opt = new OptionsBuilder() - .include(JmhHello.class.getSimpleName()) - .forks(1) - .warmupIterations(5) - .measurementIterations(10) - .build(); - new Runner(opt).run(); -} -``` - -## 正确的微基准测试 - -如果编写的代码本身就存在着诸多问题,那么即使使用正确的测试方法,也不可能得到正确的测试结果。这些测试代码中的问题应该由我们进行主动避免,那么有哪些常见问题呢?下面介绍两种最常见的情况。 - -### 无用代码消除 ( Dead Code Elimination ) - -也有网友形象的翻译成**死代码**,死代码是指那些 JVM 经过检查发现的根本不会使用到的代码。比如下面这个代码片段。 - -```java -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -/** - *

- * 测试死代码消除 - * - * @author niujinpeng - * @Date 2020/8/21 8:04 - */ -@BenchmarkMode(Mode.AverageTime) -@State(Scope.Thread) -@Fork(1) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Warmup(iterations = 3, time = 3) -@Measurement(iterations = 5, time = 3) -public class JmhDCE { - - @Benchmark - public double test1() { - return Math.log(Math.PI); - } - @Benchmark - public void test2() { - double result = Math.log(Math.PI); - result = Math.log(result); - } - - public static void main(String[] args) throws RunnerException { - Options opt = new OptionsBuilder() - .include(JmhDCE.class.getSimpleName()) - .build(); - new Runner(opt).run(); - } -} -``` - -在这个代码片段里里,`test1` 方法对圆周率进行对数计算,并返回计算结果;而 `test2` 中不仅对圆周率进行对数计算,还对计算的结果再次对数计算,看起来复杂一些,但是因为没有用到计算结果,所以 JVM 会自动消除这段代码, 因为它没有任何意义。 - -```shell -Benchmark Mode Cnt Score Error Units -JmhDCE.test1 avgt 5 0.002 ± 0.001 us/op -JmhDCE.test2 avgt 5 ≈ 10⁻⁴ us/op -``` - -测试结果里也可以看到 `test` 平均耗时 0.0004 微秒,而 `test1` 平均耗时 0.002 微秒。 - -### 常量折叠 (Constant Folding) - -在对 Java 源文件编译的过程中,编译器通过语法分析,可以发现某些能直接得到计算结果而不会再次更改的代码,然后会将计算结果记录下来,这样在执行的过程中就不需要再次运算了。比如这段代码。 - -```java -import java.util.concurrent.TimeUnit; -import org.openjdk.jmh.annotations.*; -import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.RunnerException; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; - -/** - *

- * 测试常量折叠 - * - * @author niujinpeng - * @Date 2020/8/21 8:23 - */ -@BenchmarkMode(Mode.AverageTime) -@State(Scope.Thread) -@Fork(1) -@OutputTimeUnit(TimeUnit.MICROSECONDS) -@Warmup(iterations = 3, time = 3) -@Measurement(iterations = 5, time = 3) -public class JmhConstantFolding { - - final double PI1 = 3.14159265358979323846; - double PI2 = 3.14159265358979323846; - - @Benchmark - public double test1() { - return Math.log(PI1) * Math.log(PI1); - } - - @Benchmark - public double test2() { - return Math.log(PI2) * Math.log(PI2); - } - - public static void main(String[] args) throws RunnerException { - Options opt = new OptionsBuilder().include(JmhConstantFolding.class.getSimpleName()).build(); - new Runner(opt).run(); - } -} -``` - -`test`1 中使用 `final` 修饰的 PI1 进行对象计算,因为 PI1 不能再次更改,所以 `test1` 的计算结果必定是不会更改的,所以 JVM 会进行常量折叠优化,而 `test2` 使用的 `PI2` 可能会被修改,所以只能每次进行计算。 - -```shell -Benchmark Mode Cnt Score Error Units -JmhConstantFolding.test1 avgt 5 0.002 ± 0.001 us/op -JmhConstantFolding.test2 avgt 5 0.019 ± 0.001 us/op -``` - -可以看到 `test2` 耗时要多的多,达到了 0.019 微秒。 - -其实 JVM 做的优化操作远不止上面这些,还有比如常量传播(Constant Propagation)、循环展开(Loop Unwinding)、循环表达式外提(Loop Expression Hoisting)、消除公共子表达式(Common Subexpression Elimination)、本块重排序(Basic Block Reordering)、范围检查消除(Range Check Elimination)等。 - -## 总结 - -JMH 进行基准测试的使用过程并不复杂,同为 Java 虚拟机团队开发,准确性毋容置疑。但是在进行基准测试时还是要注意自己的代码问题,如果编写的要进行测试的代码本身存在问题,那么测试的结果必定是不准的。掌握了 JMH 基准测试之后,可以尝试测试一些常用的工具或者框架的性能如何,看看哪个工具的性能最好,比如 FastJSON 真的比 GSON 在进行 JSON 转换时更 Fast 吗? - - - -**参考:** - -- https://www.ibm.com/developerworks/cn/java/j-benchmark1.html - -- http://hg.openjdk.java.net/code-tools/jmh/file/tip/jmh-samples/src/main/java/org/openjdk/jmh/samples/ -- 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第11章 后端编译与优化 - - - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) diff --git a/docs/tool/tool-mybatis-generator.md b/docs/tool/tool-mybatis-generator.md deleted file mode 100644 index de7203a..0000000 --- a/docs/tool/tool-mybatis-generator.md +++ /dev/null @@ -1,132 +0,0 @@ ---- -title: 使用MyBatis Generator自动生成Model、Dao、Mapper相关代码 -date: 2017-11-01 06:58:23 -updated: 2017-11-01 06:58:23 -url: develop/tool-mybatis-generator -categories: - - 生产工具 -tags: - - Mybatis - - Mybatis Generator - - 开发工具 ---- - -> 文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,更有 Java 程序员所需要掌握的核心知识,欢迎Star和指教。 -> 欢迎关注我的[公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7),文章每周更新。 - -### MyBatis Generator? - -MyBatis是一款非常流行的开源框架,在简单的进行框架配置后可以快速的进入开发阶段,主要的工作是编写Mapper文件和相关Dao接口以及实体类,久之,麻烦,发现这个工具,可以自动化的生成Model、Dao、Mapper相关代码。可谓是是开发MyBatis神器。 - -> Mybatis generator的功能介绍:[MyBatis发电机简介](http://www.mybatis.org/generator/ "MyBatis发电机简介") - - - - -### 工具准备 -- mysql-connector-java-5.1.6.jar 用于连接数据库 - [mybatis-generator-core-1.3.5.jar](http://central.maven.org/maven2/org/mybatis/generator/mybatis-generator-core/1.3.5/mybatis-generator-core-1.3.5.jar "mybatis-generator-core-1.3.5.jar") 用于生成相关代码(点击直接下载) -- generator.xml 配置生成逻辑 - -把工具放在统一目录下 -我在此处放置在了D:/generator之下: -```shell -D:\generator>dir - -2017/07/12 10:11 3,242 generator.xml -2017/07/11 20:35 555,960 mybatis-generator-core-1.3.5.jar -2017/06/28 21:44 703,265 mysql-connector-java-5.1.6.jar - 3 个文件 1,262,467 字节 - -D:\generator> -``` -###配置generator.xml -注意修改相关信息,数据库用户名和密码,添加要生成的表名称。 -**由于配置了javaModelGenerator,需要在同目录下新建文件夹src** -```xml - - -``` - -generator.xml -```xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

-
- - -``` -### 在当前目录下运行命令进行生成 -> java -jar mybatis-generator-core-1.3.5.jar -configfile generator.xml -overwrite - -```shell -D:\generator>java -jar mybatis-generator-core-1.3.5.jar -configfile generator.xml -overwrite -MyBatis Generator finished successfully. - -D:\generator> -``` - -### 查看生成的文件 -```shell - -D:\generator\src>tree /f -卷 software 的文件夹 PATH 列表 -卷序列号为 00000042 0000:FF78 -D:. -└─net - └─codingme - ├─dao - │ PostMapper.java - │ TagMapper.java - │ - ├─mapper - │ PostMapper.xml - │ TagMapper.xml - │ - └─po - Post.java - PostWithBLOBs.java - Tag.java - - -D:\generator\src> -``` -代码已经自动生成完成,拷贝粘贴修修改改即可。 - -**最后的话** - ->文章已经收录在 [Github.com/niumoo/JavaNotes](https://github.com/niumoo/JavaNotes) ,欢迎Star和指教。更有一线大厂面试点,Java程序员需要掌握的核心知识等文章,也整理了很多我的文字,欢迎 **Star** 和完善,希望我们一起变得优秀。 - -文章有帮助可以点个「**赞**」或「**分享**」,都是支持,我都喜欢! -文章每周持续更新,要实时关注我更新的文章以及分享的干货,可以关注「 **未读代码** 」公众号或者[我的博客](https://www.wdbyte.com/)。 - -![公众号](https://cdn.jsdelivr.net/gh/niumoo/cdn-assets@439f6a5f6bd130e2aec56f3527656d6edb487b91/webinfo/weixin-public.jpg) \ No newline at end of file From 0595a072b56e3b7dd390a15fcad362dcdd6e7685 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Mon, 14 Mar 2022 21:48:46 +0800 Subject: [PATCH 010/105] =?UTF-8?q?docs:=20=E5=A2=9E=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E7=AB=A0[5=E7=A7=8D=E9=99=90=E6=B5=81=E7=AE=97=E6=B3=95?= =?UTF-8?q?=EF=BC=8C7=E7=A7=8D=E9=99=90=E6=B5=81=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E6=8C=A1=E4=BD=8F=E7=AA=81=E5=8F=91=E6=B5=81=E9=87=8F?= =?UTF-8?q?=EF=BC=9F](https://www.wdbyte.com/java/rate-limiter.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 32 ++++++++++--------- .../src/main/resources/limiter.lua | 8 +++++ .../src/main/resources/limiter2.lua | 16 ++++++++++ pom.xml | 1 + 4 files changed, 42 insertions(+), 15 deletions(-) create mode 100644 core-java-rate-limiter/src/main/resources/limiter.lua create mode 100644 core-java-rate-limiter/src/main/resources/limiter2.lua diff --git a/README.md b/README.md index d45dac2..91266de 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,23 @@ 文章内容也都可以访问网站 [https://www.wdbyte.com](https://www.wdbyte.com) 进行阅读。 +## ⏳ Java 开发 +- [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) +- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) +- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) +- [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/) +- [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/) +- [三种骚操作绕过迭代器遍历时的数据修改异常](https://www.wdbyte.com/2021/02/develop/interator-update/) +- [Guava - 拯救垃圾代码,写出优雅高效,效率提升N倍](https://www.wdbyte.com/2020/10/develop/google-guava/) +- [「1024」专属序猿的快乐,惊奇迷惑代码大赏](https://www.wdbyte.com/2020/10/2020-1024/) +- [一篇有趣的负载均衡算法实现](https://www.wdbyte.com/2020/05/algorithm/load-balancing/) +- [撸了个多线程断点续传下载器,我从中学习到了这些知识](https://www.wdbyte.com/2020/07/tool/java-download/) + +- [Java 开发的编程噩梦,这些坑你没踩过算我输](https://www.wdbyte.com/2020/08/java/java-code-standards/) +- [如何使用 Lombok 进行优雅的编码](https://www.wdbyte.com/2018/12/develop/tool-lombok/) +- [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/) + + ## 🌿 SpringBoot 2.x 教程 使用 **Spring Boot** 可以快速的创建一个基于Spring 的、独立的、生产级的应用程序,并且可以直接运行。Spring Boot 采用习惯性配置,整合大量 Spring 组建和第三方库,让你只需要少量的修改就可以轻松上手。 @@ -140,21 +157,6 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - 堆 - 图 -## ⏳ Java 开发 -- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) -- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) -- [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/) -- [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/) -- [三种骚操作绕过迭代器遍历时的数据修改异常](https://www.wdbyte.com/2021/02/develop/interator-update/) -- [Guava - 拯救垃圾代码,写出优雅高效,效率提升N倍](https://www.wdbyte.com/2020/10/develop/google-guava/) -- [「1024」专属序猿的快乐,惊奇迷惑代码大赏](https://www.wdbyte.com/2020/10/2020-1024/) -- [一篇有趣的负载均衡算法实现](https://www.wdbyte.com/2020/05/algorithm/load-balancing/) -- [撸了个多线程断点续传下载器,我从中学习到了这些知识](https://www.wdbyte.com/2020/07/tool/java-download/) - -- [Java 开发的编程噩梦,这些坑你没踩过算我输](https://www.wdbyte.com/2020/08/java/java-code-standards/) -- [如何使用 Lombok 进行优雅的编码](https://www.wdbyte.com/2018/12/develop/tool-lombok/) -- [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/) - ## 🧰 工具技巧 >“工欲善其事,必先利其器” diff --git a/core-java-rate-limiter/src/main/resources/limiter.lua b/core-java-rate-limiter/src/main/resources/limiter.lua new file mode 100644 index 0000000..ecafbed --- /dev/null +++ b/core-java-rate-limiter/src/main/resources/limiter.lua @@ -0,0 +1,8 @@ +local count = redis.call("incr",KEYS[1]) +if count == 1 then + redis.call('expire',KEYS[1],ARGV[2]) +end +if count > tonumber(ARGV[1]) then + return 0 +end +return 1 \ No newline at end of file diff --git a/core-java-rate-limiter/src/main/resources/limiter2.lua b/core-java-rate-limiter/src/main/resources/limiter2.lua new file mode 100644 index 0000000..f776975 --- /dev/null +++ b/core-java-rate-limiter/src/main/resources/limiter2.lua @@ -0,0 +1,16 @@ +--KEYS[1]: 限流 key +--ARGV[1]: 时间戳 - 时间窗口 +--ARGV[2]: 当前时间戳(作为score) +--ARGV[3]: 阈值 +--ARGV[4]: score 对应的唯一value +-- 1. 移除时间窗口之前的数据 +redis.call('zremrangeByScore', KEYS[1], 0, ARGV[1]) +-- 2. 统计当前元素数量 +local res = redis.call('zcard', KEYS[1]) +-- 3. 是否超过阈值 +if (res == nil) or (res < tonumber(ARGV[3])) then + redis.call('zadd', KEYS[1], ARGV[2], ARGV[4]) + return 1 +else + return 0 +end \ No newline at end of file diff --git a/pom.xml b/pom.xml index 4667231..1bc2db9 100644 --- a/pom.xml +++ b/pom.xml @@ -9,6 +9,7 @@ 1.0.0-SNAPSHOT core-java-modules + core-java-rate-limiter parent-modules Parent for all java modules From 813cdecc09ca30c9fb5f6c69422533e385ec82b5 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Mon, 14 Mar 2022 21:49:53 +0800 Subject: [PATCH 011/105] =?UTF-8?q?docs:=20=E5=A2=9E=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E7=AB=A0[5=E7=A7=8D=E9=99=90=E6=B5=81=E7=AE=97=E6=B3=95?= =?UTF-8?q?=EF=BC=8C7=E7=A7=8D=E9=99=90=E6=B5=81=E6=96=B9=E5=BC=8F?= =?UTF-8?q?=EF=BC=8C=E6=8C=A1=E4=BD=8F=E7=AA=81=E5=8F=91=E6=B5=81=E9=87=8F?= =?UTF-8?q?=EF=BC=9F](https://www.wdbyte.com/java/rate-limiter.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-rate-limiter/README.md | 6 + core-java-rate-limiter/pom.xml | 20 ++++ .../wdbyte/rate/limiter/RateLimiterGuava.java | 22 ++++ .../rate/limiter/RateLimiterSildingLog.java | 74 ++++++++++++ .../rate/limiter/RateLimiterSimpleWindow.java | 45 +++++++ .../limiter/RateLimiterSimpleWindow0.java | 43 +++++++ .../limiter/RateLimiterSlidingWindow.java | 112 ++++++++++++++++++ .../src/test/java/RedisLuaLimiterByIncr.java | 46 +++++++ .../src/test/java/RedisLuaLimiterByZset.java | 49 ++++++++ 9 files changed, 417 insertions(+) create mode 100644 core-java-rate-limiter/README.md create mode 100644 core-java-rate-limiter/pom.xml create mode 100644 core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java create mode 100644 core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java create mode 100644 core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java create mode 100644 core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java create mode 100644 core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java create mode 100644 core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java create mode 100644 core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java diff --git a/core-java-rate-limiter/README.md b/core-java-rate-limiter/README.md new file mode 100644 index 0000000..55bafb2 --- /dev/null +++ b/core-java-rate-limiter/README.md @@ -0,0 +1,6 @@ +## core-java-rate-limiter +当前模块包含限流相关代码 + +### 相关文章 + +- [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) diff --git a/core-java-rate-limiter/pom.xml b/core-java-rate-limiter/pom.xml new file mode 100644 index 0000000..9679d8d --- /dev/null +++ b/core-java-rate-limiter/pom.xml @@ -0,0 +1,20 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.rate.limiter + core-java-rate-limiter + + + 17 + 17 + + + \ No newline at end of file diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java new file mode 100644 index 0000000..1f8297d --- /dev/null +++ b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java @@ -0,0 +1,22 @@ +package com.wdbyte.rate.limiter; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +import com.google.common.util.concurrent.RateLimiter; + +/** + * @author https://www.wdbyte.com + * @date 2022/02/25 + */ +public class RateLimiterGuava { + + public static void main(String[] args) throws InterruptedException { + RateLimiter rateLimiter = RateLimiter.create(2); + for (int i = 0; i < 10; i++) { + String time = LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME); + System.out.println(time + ":" + rateLimiter.tryAcquire(i+1)); + Thread.sleep(250); + } + } +} diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java new file mode 100644 index 0000000..dbc7b3f --- /dev/null +++ b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java @@ -0,0 +1,74 @@ +package com.wdbyte.rate.limiter; + +import java.time.LocalTime; +import java.util.HashSet; +import java.util.Set; +import java.util.TreeMap; + +/** + * 滑动日志方式限流 + * 设置 QPS 为 2. + * + * @author https://www.wdbyte.com + * @date 2022/02/23 + */ +public class RateLimiterSildingLog { + + /** + * 阈值 + */ + private Integer qps = 2; + /** + * 记录请求的时间戳,和数量 + */ + private TreeMap treeMap = new TreeMap<>(); + + /** + * 清理请求记录间隔, 60 秒 + */ + private long claerTime = 60 * 1000; + + public RateLimiterSildingLog(Integer qps) { + this.qps = qps; + } + + public synchronized boolean tryAcquire() { + long now = System.currentTimeMillis(); + // 清理过期的数据老数据,最长60 秒清理一次 + if (!treeMap.isEmpty() && (treeMap.firstKey() - now) > claerTime) { + Set keySet = new HashSet<>(treeMap.subMap(0L, now - 1000).keySet()); + for (Long key : keySet) { + treeMap.remove(key); + } + } + // 计算当前请求次数 + int sum = 0; + for (Long value : treeMap.subMap(now - 1000, now).values()) { + sum += value; + } + // 超过QPS限制,直接返回 false + if (sum + 1 > qps) { + return false; + } + // 记录本次请求 + if (treeMap.containsKey(now)) { + treeMap.compute(now, (k, v) -> v + 1); + } else { + treeMap.put(now, 1L); + } + return sum <= qps; + } + + public static void main(String[] args) throws InterruptedException { + RateLimiterSildingLog rateLimiterSildingLog = new RateLimiterSildingLog(3); + for (int i = 0; i < 10; i++) { + Thread.sleep(250); + LocalTime now = LocalTime.now(); + if (rateLimiterSildingLog.tryAcquire()) { + System.out.println(now + " 做点什么"); + } else { + System.out.println(now + " 被限流"); + } + } + } +} diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java new file mode 100644 index 0000000..913316c --- /dev/null +++ b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java @@ -0,0 +1,45 @@ +package com.wdbyte.rate.limiter; + +import java.time.LocalTime; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 固定窗口限流实现(不需要开单独线程) + * + * @author https://www.wdbyte.com + * @date 2022/02/23 + */ +public class RateLimiterSimpleWindow { + + // 阈值 + private static Integer QPS = 2; + // 时间窗口(毫秒) + private static long TIME_WINDOWS = 1000; + // 计数器 + private static AtomicInteger REQ_COUNT = new AtomicInteger(); + + private static long START_TIME = System.currentTimeMillis(); + + public synchronized static boolean tryAcquire() { + long now = System.currentTimeMillis(); + if ((now - START_TIME) > TIME_WINDOWS) { + START_TIME = now; + REQ_COUNT.set(1); + return true; + } + return REQ_COUNT.incrementAndGet() <= QPS; + } + + public static void main(String[] args) throws InterruptedException { + //Thread.sleep(400); + for (int i = 0; i < 10; i++) { + Thread.sleep(250); + LocalTime now = LocalTime.now(); + if (!tryAcquire()) { + System.out.println(now + " 被限流"); + } else { + System.out.println(now + " 做点什么"); + } + } + } +} diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java new file mode 100644 index 0000000..5f5b872 --- /dev/null +++ b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java @@ -0,0 +1,43 @@ +package com.wdbyte.rate.limiter; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 固定窗口限流的简单实现 + * 需要开线程 + * + * @author https://www.wdbyte.com + * @date 2022/02/23 + */ +public class RateLimiterSimpleWindow0 { + + // 阈值 + private static Integer qps = 2; + // 计数器 + private static AtomicInteger reqCount = new AtomicInteger(); + + static { + new Thread(() -> { + while (true) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + reqCount.getAndSet(0); + } + }).start(); + } + + public static void main(String[] args) throws InterruptedException { + for (int i = 0; i < 10; i++) { + Thread.sleep(250); + if (reqCount.getAndAdd(1) > qps) { + System.out.println("被限流"); + } else { + reqCount.incrementAndGet(); + System.out.println("做点什么"); + } + } + } +} diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java new file mode 100644 index 0000000..503b047 --- /dev/null +++ b/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java @@ -0,0 +1,112 @@ +package com.wdbyte.rate.limiter; + +import java.time.LocalTime; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 滑动窗口限流工具类 + * + * @author https://www.wdbyte.com + * @date 2022/02/23 + */ +public class RateLimiterSlidingWindow { + /** + * 阈值 + */ + private int qps = 2; + /** + * 时间窗口总大小(毫秒) + */ + private long windowSize = 1000; + /** + * 多少个子窗口 + */ + private Integer windowCount = 10; + /** + * 窗口列表 + */ + private WindowInfo[] windowArray = new WindowInfo[windowCount]; + + public RateLimiterSlidingWindow(int qps) { + this.qps = qps; + long currentTimeMillis = System.currentTimeMillis(); + for (int i = 0; i < windowArray.length; i++) { + windowArray[i] = new WindowInfo(currentTimeMillis, new AtomicInteger(0)); + } + } + + /** + * 1. 计算当前时间窗口 + * 2. 更新当前窗口计数 & 重置过期窗口计数 + * 3. 当前 QPS 是否超过限制 + * + * @return + */ + public synchronized boolean tryAcquire() { + long currentTimeMillis = System.currentTimeMillis(); + // 1. 计算当前时间窗口 + int currentIndex = (int)(currentTimeMillis % windowSize / (windowSize / windowCount)); + // 2. 更新当前窗口计数 & 重置过期窗口计数 + int sum = 0; + for (int i = 0; i < windowArray.length; i++) { + WindowInfo windowInfo = windowArray[i]; + if ((currentTimeMillis - windowInfo.getTime()) > windowSize) { + windowInfo.getNumber().set(0); + windowInfo.setTime(currentTimeMillis); + } + if (currentIndex == i && windowInfo.getNumber().get() < qps) { + windowInfo.getNumber().incrementAndGet(); + } + sum = sum + windowInfo.getNumber().get(); + } + // 3. 当前 QPS 是否超过限制 + return sum <= qps; + } + + private class WindowInfo { + // 窗口开始时间 + private Long time; + // 计数器 + private AtomicInteger number; + + public WindowInfo(long time, AtomicInteger number) { + this.time = time; + this.number = number; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public AtomicInteger getNumber() { + return number; + } + } + + public static void main(String[] args) throws InterruptedException { + int qps = 2, count = 20, sleep = 300, success = count * sleep / 1000 * qps; + System.out.println(String.format("当前QPS限制为:%d,当前测试次数:%d,间隔:%dms,预计成功次数:%d", qps, count, sleep, success)); + success = 0; + RateLimiterSlidingWindow myRateLimiter = new RateLimiterSlidingWindow(qps); + for (int i = 0; i < count; i++) { + Thread.sleep(sleep); + if (myRateLimiter.tryAcquire()) { + success++; + if (success % qps == 0) { + System.out.println(LocalTime.now() + ": success, "); + } else { + System.out.print(LocalTime.now() + ": success, "); + } + } else { + System.out.println(LocalTime.now() + ": fail"); + } + } + System.out.println(); + System.out.println("实际测试成功次数:" + success); + } +} + diff --git a/core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java b/core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java new file mode 100644 index 0000000..2faf302 --- /dev/null +++ b/core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java @@ -0,0 +1,46 @@ +//import java.io.IOException; +//import java.time.LocalTime; +//import java.util.Arrays; +// +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.core.io.ClassPathResource; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.data.redis.core.script.DefaultRedisScript; +//import org.springframework.scripting.support.ResourceScriptSource; +// +//@SpringBootTest +//class RedisLuaLimiterByIncr { +// private static String KEY_PREFIX = "limiter_"; +// private static String QPS = "4"; +// private static String EXPIRE_TIME = "1"; +// +// @Autowired +// private StringRedisTemplate stringRedisTemplate; +// +// @Test +// public void redisLuaLimiterTests() throws InterruptedException, IOException { +// for (int i = 0; i < 15; i++) { +// Thread.sleep(200); +// System.out.println(LocalTime.now() + " " + acquire("user1")); +// } +// } +// +// /** +// * 计数器限流 +// * +// * @param key +// * @return +// */ +// public boolean acquire(String key) { +// // 当前秒数作为 key +// key = KEY_PREFIX + key + System.currentTimeMillis() / 1000; +// DefaultRedisScript redisScript = new DefaultRedisScript<>(); +// redisScript.setResultType(Long.class); +// //lua文件存放在resources目录下 +// redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limiter.lua"))); +// return stringRedisTemplate.execute(redisScript, Arrays.asList(key), QPS, EXPIRE_TIME) == 1; +// } +// +//} diff --git a/core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java b/core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java new file mode 100644 index 0000000..2b784e8 --- /dev/null +++ b/core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java @@ -0,0 +1,49 @@ +//import java.io.IOException; +//import java.time.LocalTime; +//import java.util.Arrays; +// +//import org.junit.jupiter.api.Test; +//import org.springframework.beans.factory.annotation.Autowired; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.core.io.ClassPathResource; +//import org.springframework.data.redis.core.StringRedisTemplate; +//import org.springframework.data.redis.core.script.DefaultRedisScript; +//import org.springframework.scripting.support.ResourceScriptSource; +// +//@SpringBootTest +//class RedisLuaLimiterByZset { +// +// private String KEY_PREFIX = "limiter_"; +// private String QPS = "4"; +// +// @Autowired +// private StringRedisTemplate stringRedisTemplate; +// +// @Test +// public void redisLuaLimiterTests() throws InterruptedException, IOException { +// for (int i = 0; i < 15; i++) { +// Thread.sleep(200); +// System.out.println(LocalTime.now() + " " + acquire("user1")); +// } +// } +// +// /** +// * 计数器限流 +// * +// * @param key +// * @return +// */ +// public boolean acquire(String key) { +// long now = System.currentTimeMillis(); +// key = KEY_PREFIX + key; +// String oldest = String.valueOf(now - 1_000); +// String score = String.valueOf(now); +// String scoreValue = score; +// DefaultRedisScript redisScript = new DefaultRedisScript<>(); +// redisScript.setResultType(Long.class); +// //lua文件存放在resources目录下 +// redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("limiter2.lua"))); +// return stringRedisTemplate.execute(redisScript, Arrays.asList(key), oldest, score, QPS, scoreValue) == 1; +// } +// +//} From 784ec5d9e95b59882ab0d1b995a0f5309cc44e2f Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 6 Apr 2022 19:45:23 +0800 Subject: [PATCH 012/105] =?UTF-8?q?docs:=20[Java=208=20Lambda=20=E5=92=8C?= =?UTF-8?q?=20Comparator=20=E6=8E=92=E5=BA=8F](/java8/comparator/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-8/README.md | 1 + core-java-modules/core-java-8/pom.xml | 24 +++++++ .../main/java/com/wdbyte/Jdk8Function.java | 4 -- .../src/main/java/com/wdbyte/Jdk8Lambda.java | 2 +- .../java/com/wdbyte/Jdk8LocalDateTime.java | 2 +- .../main/java/com/wdbyte/Jdk8NashornJs.java | 6 +- .../main/java/com/wdbyte/Jdk8Optional.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Stream.java | 3 +- .../main/java/com/wdbyte/Jdk8StreamPro.java | 3 +- .../wdbyte/comparator/Java8Comparator.java | 66 +++++++++++++++++++ .../wdbyte/comparator/Java8Comparator2.java | 29 ++++++++ .../wdbyte/comparator/Java8Comparator3.java | 28 ++++++++ .../wdbyte/comparator/Java8Comparator4.java | 28 ++++++++ .../wdbyte/comparator/Java8Comparator5.java | 30 +++++++++ .../wdbyte/comparator/Java8Comparator6.java | 36 ++++++++++ .../wdbyte/comparator/Java8Comparator7.java | 28 ++++++++ 16 files changed, 277 insertions(+), 15 deletions(-) create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java create mode 100644 core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java diff --git a/core-java-modules/core-java-8/README.md b/core-java-modules/core-java-8/README.md index b27f4f4..edcb82b 100644 --- a/core-java-modules/core-java-8/README.md +++ b/core-java-modules/core-java-8/README.md @@ -2,6 +2,7 @@ 当前模块包含 Java 8 新特性相关代码 ### 相关文章 +- [Java 8 Lambda 和 Comparator 排序](/java8/comparator/) - [Java 8 函数接口 UnaryOperator ](https://www.wdbyte.com/java8/java8-unaryoperaotr) - [Java 8 函数接口 BiPredicate ](https://www.wdbyte.com/java8/java8-bipredicate) - [Java 8 函数接口 BiFunction ](https://www.wdbyte.com/java8/java8-bifunction/) diff --git a/core-java-modules/core-java-8/pom.xml b/core-java-modules/core-java-8/pom.xml index a8cb9fc..2ae8b99 100644 --- a/core-java-modules/core-java-8/pom.xml +++ b/core-java-modules/core-java-8/pom.xml @@ -18,4 +18,28 @@ 1.8 + + + + org.junit + junit-bom + 5.8.2 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter + + + org.projectlombok + lombok + 1.18.22 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java index 2919164..78d045b 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.function.Predicate; -import org.junit.Test; /** *

@@ -20,7 +19,6 @@ */ public class Jdk8Function { - @Test public void testFunction() { // 构造器引用 final Car bmwCar = Car.create(Car::new); @@ -39,7 +37,6 @@ public void testFunction() { /** * 函数接口 */ - @Test public void functionInterfaceTest() { List skills = Arrays.asList("java", "golang", "c++", "c", "python"); Predicate length4 = (str) -> str.length() > 4; @@ -75,7 +72,6 @@ public void filterByFilter(List list, Predicate condition) { /** * predicate 的复杂使用 */ - @Test public void predicateTest() { Predicate startsWith = (str) -> str.startsWith("g"); Predicate length = (str) -> str.length() > 4; diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java index 46804b4..b2663cb 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java @@ -4,7 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.util.*; import java.util.stream.Collectors; diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java index b850e40..360e967 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java @@ -6,7 +6,7 @@ import java.time.temporal.TemporalAdjusters; import java.util.Date; -import org.junit.Test; +import org.junit.jupiter.api.Test; /** *

diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java index 5389aa9..6a1a2e7 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java @@ -1,12 +1,10 @@ package com.wdbyte; -import org.junit.Test; - import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; -import java.util.HashSet; -import java.util.Set; + +import org.junit.jupiter.api.Test; /** *

diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java index ba276c2..4288b27 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java @@ -2,9 +2,9 @@ import java.util.Optional; -import org.junit.Test; import lombok.Data; +import org.junit.jupiter.api.Test; /** *

diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java index 9a9ca9c..aa1a6b5 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java @@ -1,7 +1,6 @@ package com.wdbyte; -import org.junit.Test; - +import org.junit.jupiter.api.Test; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8StreamPro.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8StreamPro.java index d4a2e87..18c400e 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8StreamPro.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8StreamPro.java @@ -6,8 +6,7 @@ import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Stream; - -import org.junit.Test; +import org.junit.jupiter.api.Test; /** * @author darcy diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java new file mode 100644 index 0000000..78f7109 --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java @@ -0,0 +1,66 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + list.add(new Person("Chris", 20)); + sortListJava7(list); + } + + private static void sortListJava7(List list) { + Collections.sort(list, new Comparator() { + @Override + public int compare(Person o1, Person o2) { + return o1.getAge() - o2.getAge(); + } + }); + for (Person person : list) { + System.out.println(person); + } + } +} + +class Person { + private String name; + private Integer age; + + public Person(String name, Integer age) { + this.name = name; + this.age = age; + } + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } +} diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java new file mode 100644 index 0000000..bef75cc --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java @@ -0,0 +1,29 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator2 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Chris", 20)); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + sort(list); + list.forEach(System.out::println); + } + + private static List sort(List list) { + //Comparator byAge = (Person o1, Person o2) -> o1.getAge().compareTo(o2.getAge()); + Comparator byAge = Comparator.comparing(Person::getAge); + list.sort(byAge); + return list; + } +} + diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java new file mode 100644 index 0000000..70d3a24 --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java @@ -0,0 +1,28 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator3 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Chris", 20)); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + sort(list); + for (Person person : list) { + System.out.println(person); + } + } + + private static List sort(List list) { + list.sort(Comparator.comparing(Person::getAge)); + return list; + } +} + diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java new file mode 100644 index 0000000..250e30a --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java @@ -0,0 +1,28 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator4 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Chris", 20)); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + sort(list); + for (Person person : list) { + System.out.println(person); + } + } + + private static List sort(List list) { + list.sort(Comparator.comparing(Person::getName)); + return list; + } +} + diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java new file mode 100644 index 0000000..d65054b --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java @@ -0,0 +1,30 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator5 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Chris", 20)); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + Collections.sort(list, new Comparator() { + @Override + public int compare(Person o1, Person o2) { + return o1.getAge() - o2.getAge(); + } + }); + for (Person person : list) { + System.out.println(person); + } + } + +} + diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java new file mode 100644 index 0000000..bec0fb7 --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java @@ -0,0 +1,36 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator6 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new Person("Chris", 20)); + list.add(new Person("Linda", 10)); + list.add(new Person("Jack", 30)); + + Comparator comparing = Comparator.comparing(Person::getAge); + list.sort(comparing); + list.forEach(System.out::println); + + System.out.println("--------"); + + list.sort(comparing.reversed()); + list.forEach(System.out::println); + + list.sort((p1, p2) -> p1.getAge() - p2.getAge()); + list.forEach(System.out::println); + System.out.println("--------"); + list.sort((p1, p2) -> p2.getAge() - p1.getAge()); + list.forEach(System.out::println); + } + +} + diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java new file mode 100644 index 0000000..c7c9d25 --- /dev/null +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java @@ -0,0 +1,28 @@ +package com.wdbyte.comparator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * @author niulang + * @date 2022/04/02 + */ +public class Java8Comparator7 { + public static void main(String[] args) { + List list = new ArrayList<>(); + list.add(new com.wdbyte.comparator.Person("Chris", 20)); + list.add(new com.wdbyte.comparator.Person("Linda", 10)); + list.add(new com.wdbyte.comparator.Person("Jack", 30)); + + list.stream() + .sorted(Comparator.comparing(Person::getAge)) + .forEach(System.out::println); + System.out.println("----------"); + list.stream() + .sorted(Comparator.comparing(Person::getAge).reversed()) + .forEach(System.out::println); + } + +} + From 8bea84ca0b3b14ee8173bafb0d2fbd8f0c81fe0a Mon Sep 17 00:00:00 2001 From: Celebrate-future <78527112+Celebrate-future@users.noreply.github.com> Date: Mon, 18 Apr 2022 21:32:10 +0800 Subject: [PATCH 013/105] remove redundant dependencies --- core-java-modules/core-java-io/pom.xml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml index dfdad77..2fb5dba 100644 --- a/core-java-modules/core-java-io/pom.xml +++ b/core-java-modules/core-java-io/pom.xml @@ -18,4 +18,14 @@ 1.8 - \ No newline at end of file + + + com.google.guava + guava + 31.0.1-jre + jar + provided + + + + From b2b647ecaa1523e4aafc1dcdd8b558c7e0a66b91 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:44:03 +0800 Subject: [PATCH 014/105] =?UTF-8?q?docs:=20=E6=AF=8F=E6=97=A5=E4=B8=80?= =?UTF-8?q?=E9=A2=98=EF=BC=9ALeetCode388?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- leetcode/pom.xml | 19 ++++ .../java/com/wdbyte/leetcode/LeetCode388.java | 92 +++++++++++++++++++ pom.xml | 3 + 3 files changed, 114 insertions(+) create mode 100644 leetcode/pom.xml create mode 100644 leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java diff --git a/leetcode/pom.xml b/leetcode/pom.xml new file mode 100644 index 0000000..d04fbd8 --- /dev/null +++ b/leetcode/pom.xml @@ -0,0 +1,19 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + leetcode + + + 8 + 8 + + + \ No newline at end of file diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java new file mode 100644 index 0000000..e2f3a27 --- /dev/null +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java @@ -0,0 +1,92 @@ +package com.wdbyte.leetcode; + +import java.util.Stack; + +/** + * 388. 文件的最长绝对路径 + * https://leetcode-cn.com/problems/longest-absolute-file-path/ + * + * @author niulang + * @date 2022/04/20 + */ +public class LeetCode388 { + public static void main(String[] args) { + LeetCode388 leetCode388 = new LeetCode388(); + //int path = leetCode388.lengthLongestPath2("dir\n file.txt"); + int path = leetCode388.lengthLongestPath2("dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext"); + System.out.println(path); + } + + public int lengthLongestPath(String input) { + String[] array = input.split("\n"); + Stack stack = new Stack(); + int lastTCount = 0; + int maxSize = 0; + for (String path : array) { + int tCount = 0; + while (path.contains("\t")) { + tCount++; + path = path.substring(path.indexOf("\t") + 1); + } + if (tCount > lastTCount) { + stack.push(path); + } else { + for (int i = 0; i <= (lastTCount - tCount); i++) { + if (!stack.isEmpty()) { + stack.pop(); + } + } + stack.push(path); + } + lastTCount = tCount; + if (path.contains(".")) { + int size = 0; + for (String s : stack) { + size += s.length(); + } + size = size + stack.size() - 1; + if (size > maxSize) { + maxSize = size; + } + } + } + return maxSize; + } + + public int lengthLongestPath2(String input) { + String[] array = input.split("\n"); + Stack stack = new Stack(); + int lastTCount = 0; + int maxSize = 0; + for (String path : array) { + int tCount = 0; + while (path.contains("\t")) { + tCount++; + path = path.substring(path.indexOf("\t") + 1); + } + if (tCount > lastTCount) { + stack.push(path.length()); + } else { + for (int i = 0; i <= (lastTCount - tCount); i++) { + if (!stack.isEmpty()) { + stack.pop(); + } + } + stack.push(path.length()); + } + lastTCount = tCount; + if (path.contains(".")) { + int size = 0; + for (Integer s : stack) { + size += s; + } + size = size + stack.size() - 1; + if (size > maxSize) { + maxSize = size; + } + } + } + return maxSize; + } + +} diff --git a/pom.xml b/pom.xml index 1bc2db9..ff351cb 100644 --- a/pom.xml +++ b/pom.xml @@ -10,6 +10,9 @@ core-java-modules core-java-rate-limiter + junit5-jupiter-starter + apache-httpclient + leetcode parent-modules Parent for all java modules From fd99ce1fe755f02bc8e24faf1f9832d24c28ebc5 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:44:57 +0800 Subject: [PATCH 015/105] =?UTF-8?q?=E9=87=8D=E6=9E=84=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- junit5-jupiter-starter/.gitignore | 26 +++++++++++++++ .../java/com/wdbyte/junit5/Calculator.java | 32 +++++++++++++++++++ .../com/wdbyte/junit5/CalculatorTest.java | 30 +++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 junit5-jupiter-starter/.gitignore create mode 100644 junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java diff --git a/junit5-jupiter-starter/.gitignore b/junit5-jupiter-starter/.gitignore new file mode 100644 index 0000000..6aa4da5 --- /dev/null +++ b/junit5-jupiter-starter/.gitignore @@ -0,0 +1,26 @@ +/target/ +*/systemlog/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/% \ No newline at end of file diff --git a/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java b/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java new file mode 100644 index 0000000..56a1037 --- /dev/null +++ b/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java @@ -0,0 +1,32 @@ +package com.wdbyte.junit5; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.Charset; + +/** + * @author niulang + * @date 2022/03/16 + */ +public class Calculator { + + public int add(int x, int y) { + return x + y; + } + public static void main(String[] args) throws UnknownHostException { + InetAddress byName = InetAddress.getByName("www.baidu.com"); + System.out.println(byName.getHostAddress()); + //Java使用的默认字符集 + System.out.println("java 默认字符集:"); + System.out.println(Charset.defaultCharset()+"\n"); + //汉字“测”的字节编码 + String str = "测试一下"; + //这里可以手动设置编码字符集,默认使用utf-8编码 + byte[] bytes = str.getBytes(); + System.out.println("汉字\"测\"的编码:"); + for(byte bt: bytes){ + System.out.println(bt); + } + + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java new file mode 100644 index 0000000..2005ed6 --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java @@ -0,0 +1,30 @@ +package com.wdbyte.junit5; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author niulang + * @date 2022/03/16 + */ +@DisplayName("计算器") +class CalculatorTest { + + private final Calculator calculator = new Calculator(); + + //@Tag("fast") + @Test + //@RepeatedTest(2) + @DisplayName("相加") + //@ParameterizedTest() + //@ValueSource(ints = { -1, -4 }) + void add() { + assertEquals(3, calculator.add(1, 1)); + } +} \ No newline at end of file From 087e329c1fe8392fdf1d605a59f2faedd2243652 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:45:54 +0800 Subject: [PATCH 016/105] add .gitignore --- apache-httpclient/.gitignore | 26 ++++++++++++++++++++++++ core-java-modules/.gitignore | 26 ++++++++++++++++++++++++ core-java-modules/core-java-8/.gitignore | 26 ++++++++++++++++++++++++ leetcode/.gitignore | 26 ++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 apache-httpclient/.gitignore create mode 100644 core-java-modules/.gitignore create mode 100644 core-java-modules/core-java-8/.gitignore create mode 100644 leetcode/.gitignore diff --git a/apache-httpclient/.gitignore b/apache-httpclient/.gitignore new file mode 100644 index 0000000..6aa4da5 --- /dev/null +++ b/apache-httpclient/.gitignore @@ -0,0 +1,26 @@ +/target/ +*/systemlog/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/% \ No newline at end of file diff --git a/core-java-modules/.gitignore b/core-java-modules/.gitignore new file mode 100644 index 0000000..6aa4da5 --- /dev/null +++ b/core-java-modules/.gitignore @@ -0,0 +1,26 @@ +/target/ +*/systemlog/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/% \ No newline at end of file diff --git a/core-java-modules/core-java-8/.gitignore b/core-java-modules/core-java-8/.gitignore new file mode 100644 index 0000000..6aa4da5 --- /dev/null +++ b/core-java-modules/core-java-8/.gitignore @@ -0,0 +1,26 @@ +/target/ +*/systemlog/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/% \ No newline at end of file diff --git a/leetcode/.gitignore b/leetcode/.gitignore new file mode 100644 index 0000000..6aa4da5 --- /dev/null +++ b/leetcode/.gitignore @@ -0,0 +1,26 @@ +/target/ +*/systemlog/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/build/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/% \ No newline at end of file From d30ddc5d7b544e4eb079dbbf53dd01d135f28c8e Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:47:25 +0800 Subject: [PATCH 017/105] update --- core-java-modules/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 7dfb70a..80bd3fc 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -25,6 +25,7 @@ core-java-performance-code core-java-io core-java-string + core-java-18 \ No newline at end of file From e3308161df6044fa05436b3e7bab5065493a312d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:49:04 +0800 Subject: [PATCH 018/105] docs: add README.md --- apache-httpclient/pom.xml | 54 ++++++++++++++++++++++++++ core-java-modules/core-java-18/pom.xml | 20 ++++++++++ junit5-jupiter-starter/pom.xml | 53 +++++++++++++++++++++++++ leetcode/README.md | 15 +++++++ 4 files changed, 142 insertions(+) create mode 100644 apache-httpclient/pom.xml create mode 100644 core-java-modules/core-java-18/pom.xml create mode 100644 junit5-jupiter-starter/pom.xml create mode 100644 leetcode/README.md diff --git a/apache-httpclient/pom.xml b/apache-httpclient/pom.xml new file mode 100644 index 0000000..36e0086 --- /dev/null +++ b/apache-httpclient/pom.xml @@ -0,0 +1,54 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.httpclient + apache-httpclient + + + 17 + 17 + + + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.1.3 + + + + org.junit.jupiter + junit-jupiter + test + + + + + org.apache.httpcomponents.client5 + httpclient5-fluent + 5.1.3 + + + + + + + + org.junit + junit-bom + 5.8.2 + pom + import + + + + \ No newline at end of file diff --git a/core-java-modules/core-java-18/pom.xml b/core-java-modules/core-java-18/pom.xml new file mode 100644 index 0000000..540dc8f --- /dev/null +++ b/core-java-modules/core-java-18/pom.xml @@ -0,0 +1,20 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.jdk18 + core-java-18 + + + 18 + 18 + + + \ No newline at end of file diff --git a/junit5-jupiter-starter/pom.xml b/junit5-jupiter-starter/pom.xml new file mode 100644 index 0000000..b41ccff --- /dev/null +++ b/junit5-jupiter-starter/pom.xml @@ -0,0 +1,53 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.junit5 + junit5-jupiter-starter + + + 17 + 17 + UTF-8 + ${maven.compiler.source} + + + + + + org.junit + junit-bom + 5.8.2 + pom + import + + + + + + + org.junit.jupiter + junit-jupiter + test + + + + + + maven-compiler-plugin + 3.8.1 + + + maven-surefire-plugin + 2.22.2 + + + + \ No newline at end of file diff --git a/leetcode/README.md b/leetcode/README.md new file mode 100644 index 0000000..d276c6f --- /dev/null +++ b/leetcode/README.md @@ -0,0 +1,15 @@ +## LeetCode + +| 日期 | 题目 | 解题 | +| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 2022-04-20 | [LeetCode 388. 文件的最长绝对路径](https://leetcode-cn.com/problems/longest-absolute-file-path/) | [LeetCode388.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java) | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | +| | | | + + From d4c7056dffefc545e9e96efff58268a93ac8c587 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 20 Apr 2022 20:50:31 +0800 Subject: [PATCH 019/105] docs: add README.md --- leetcode/README.md | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/leetcode/README.md b/leetcode/README.md index d276c6f..a32f375 100644 --- a/leetcode/README.md +++ b/leetcode/README.md @@ -1,15 +1,11 @@ ## LeetCode -| 日期 | 题目 | 解题 | -| ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 日期 | 题目 | 解题 | +|------------| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 2022-04-24 | | | +| 2022-04-23 | | | +| 2022-04-22 | | | +| 2022-04-21 | | | | 2022-04-20 | [LeetCode 388. 文件的最长绝对路径](https://leetcode-cn.com/problems/longest-absolute-file-path/) | [LeetCode388.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java) | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | -| | | | From 2aeb98aa0840554df24d47933e65aa93aad1c63b Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 21 Apr 2022 14:02:01 +0800 Subject: [PATCH 020/105] leetcode: 2022-04-21 LeetCode824.java --- leetcode/README.md | 12 ++-- .../java/com/wdbyte/leetcode/LeetCode824.java | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java diff --git a/leetcode/README.md b/leetcode/README.md index a32f375..0d370d7 100644 --- a/leetcode/README.md +++ b/leetcode/README.md @@ -1,11 +1,11 @@ ## LeetCode -| 日期 | 题目 | 解题 | -|------------| ------------------------------------------------------------ | ------------------------------------------------------------ | -| 2022-04-24 | | | -| 2022-04-23 | | | -| 2022-04-22 | | | -| 2022-04-21 | | | +| 日期 | 题目 | 解题 | +|------------|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| +| 2022-04-24 | | | +| 2022-04-23 | | | +| 2022-04-22 | | | +| 2022-04-21 | [LeetCode 824. 山羊拉丁文](https://leetcode-cn.com/problems/goat-latin/) | [LeetCode824.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java) | | 2022-04-20 | [LeetCode 388. 文件的最长绝对路径](https://leetcode-cn.com/problems/longest-absolute-file-path/) | [LeetCode388.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java) | diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java new file mode 100644 index 0000000..9a55a09 --- /dev/null +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java @@ -0,0 +1,57 @@ +package com.wdbyte.leetcode; + +/** + * https://leetcode-cn.com/problems/goat-latin/ + * 824. 山羊拉丁文 + * + * @author niulang + * @date 2022/04/21 + */ +public class LeetCode824 { + + public static void main(String[] args) { + //String goatLatin = new LeetCode824().toGoatLatin("I speak Goat Latin"); + String goatLatin = new LeetCode824().toGoatLatin("The quick brown fox jumped over the lazy dog"); + //String goatLatin = new LeetCode824().toGoatLatin("goat"); + System.out.println(goatLatin); + } + + /** + * 如果单词以元音开头('a', 'e', 'i', 'o', 'u'),在单词后添加"ma"。 + * 例如,单词 "apple" 变为 "applema" 。 + * 如果单词以辅音字母开头(即,非元音字母),移除第一个字符并将它放到末尾,之后再添加"ma"。 + * 例如,单词 "goat" 变为 "oatgma" 。 + * 根据单词在句子中的索引,在单词最后添加与索引相同数量的字母'a',索引从 1 开始。 + * 例如,在第一个单词后添加 "a" ,在第二个单词后添加 "aa" ,以此类推。 + * + * @param sentence + * @return + */ + public String toGoatLatin(String sentence) { + String[] array = sentence.split(" "); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + String word = array[i]; + if (builder.length() != 0) { + builder.append(" "); + } + // 元音开头 + char[] chars = word.toCharArray(); + if (chars[0] == 'A' || chars[0] == 'E' || chars[0] == 'I' || chars[0] == 'O' || chars[0] == 'U' || + chars[0] == 'a' || chars[0] == 'e' || chars[0] == 'i' || chars[0] == 'o' || chars[0] == 'u') { + builder.append(word).append("ma"); + } else { + // 辅音开头 + for (int j = 0; j < chars.length; j++) { + if (j == 0) {continue;} + builder.append(chars[j]); + } + builder.append(chars[0]).append("ma"); + } + for (int j = 0; j <= i; j++) { + builder.append("a"); + } + } + return builder.toString(); + } +} From ae711a4eedf31656fdec114061ed74ff411aadbc Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 22 Apr 2022 13:34:20 +0800 Subject: [PATCH 021/105] leetcode: 2022-04-22 LeetCode396_MaxRotateFunction.java --- leetcode/README.md | 14 ++--- ...ava => LeetCode388_LengthLongestPath.java} | 4 +- .../LeetCode396_MaxRotateFunction.java | 55 +++++++++++++++++++ ...e824.java => LeetCode824_ToGoatLatin.java} | 28 +++++++++- 4 files changed, 89 insertions(+), 12 deletions(-) rename leetcode/src/main/java/com/wdbyte/leetcode/{LeetCode388.java => LeetCode388_LengthLongestPath.java} (95%) create mode 100644 leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java rename leetcode/src/main/java/com/wdbyte/leetcode/{LeetCode824.java => LeetCode824_ToGoatLatin.java} (65%) diff --git a/leetcode/README.md b/leetcode/README.md index 0d370d7..c3684f8 100644 --- a/leetcode/README.md +++ b/leetcode/README.md @@ -1,11 +1,11 @@ ## LeetCode -| 日期 | 题目 | 解题 | -|------------|-----------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------| -| 2022-04-24 | | | -| 2022-04-23 | | | -| 2022-04-22 | | | -| 2022-04-21 | [LeetCode 824. 山羊拉丁文](https://leetcode-cn.com/problems/goat-latin/) | [LeetCode824.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java) | -| 2022-04-20 | [LeetCode 388. 文件的最长绝对路径](https://leetcode-cn.com/problems/longest-absolute-file-path/) | [LeetCode388.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java) | +| 日期 | 题目 | 解题 | +|------------|-----------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 2022-04-24 | | | +| 2022-04-23 | | | +| 2022-04-22 | [LeetCode 396. 旋转函数](https://leetcode-cn.com/problems/rotate-function/) | [LeetCode396_MaxRotateFunction.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java) | +| 2022-04-21 | [LeetCode 824. 山羊拉丁文](https://leetcode-cn.com/problems/goat-latin/) | [LeetCode824_ToGoatLatin.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java) | +| 2022-04-20 | [LeetCode 388. 文件的最长绝对路径](https://leetcode-cn.com/problems/longest-absolute-file-path/) | [LeetCode388_LengthLongestPath.java](https://github.com/niumoo/JavaNotes/blob/master/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java) | diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java similarity index 95% rename from leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java rename to leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java index e2f3a27..8a41422 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java @@ -9,9 +9,9 @@ * @author niulang * @date 2022/04/20 */ -public class LeetCode388 { +public class LeetCode388_LengthLongestPath { public static void main(String[] args) { - LeetCode388 leetCode388 = new LeetCode388(); + LeetCode388_LengthLongestPath leetCode388 = new LeetCode388_LengthLongestPath(); //int path = leetCode388.lengthLongestPath2("dir\n file.txt"); int path = leetCode388.lengthLongestPath2("dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext"); System.out.println(path); diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java new file mode 100644 index 0000000..adbe6e1 --- /dev/null +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java @@ -0,0 +1,55 @@ +package com.wdbyte.leetcode; + +import java.util.Arrays; + +/** + * https://leetcode-cn.com/problems/rotate-function/ + * 给定一个长度为 n 的整数数组nums。 + * + * 假设arrk是数组nums顺时针旋转 k 个位置后的数组,我们定义nums的 旋转函数F为: + * + * F(k) = 0 * arrk[0] + 1 * arrk[1] + ... + (n - 1) * arrk[n - 1] + * 返回F(0), F(1), ..., F(n-1)中的最大值。 + * + * 生成的测试用例让答案符合32 位 整数。 + * 例子: + * 输入: nums = [4,3,2,6] + * 输出: 26 + * 解释: + * F(0) = (0 * 4) + (1 * 3) + (2 * 2) + (3 * 6) = 0 + 3 + 4 + 18 = 25 + * F(1) = (0 * 6) + (1 * 4) + (2 * 3) + (3 * 2) = 0 + 4 + 6 + 6 = 16 + * F(2) = (0 * 2) + (1 * 6) + (2 * 4) + (3 * 3) = 0 + 6 + 8 + 9 = 23 + * F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26 + * 所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。 + * + * @author niulang + * @date 2022/04/22 + */ +public class LeetCode396_MaxRotateFunction { + + public static void main(String[] args) { + int[] nums = new int[] {4, 3, 2, 6}; + //int[] nums = new int[] {100}; + System.out.println(new LeetCode396_MaxRotateFunction().maxRotateFunction(nums)); + } + + /** + * 寻找规律 + * + * @param nums + * @return + */ + public int maxRotateFunction(int[] nums) { + int sum = 0, numSum = Arrays.stream(nums).sum(); + for (int i = 0; i < nums.length; i++) { + sum += i * nums[i]; + } + int max = sum; + for (int i = 1; i < nums.length; i++) { + // 规律 + sum = sum + numSum - nums.length * nums[nums.length - i]; + max = Math.max(max, sum); + } + return max; + } +} diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java similarity index 65% rename from leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java rename to leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java index 9a55a09..43a4c12 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java @@ -7,11 +7,11 @@ * @author niulang * @date 2022/04/21 */ -public class LeetCode824 { +public class LeetCode824_ToGoatLatin { public static void main(String[] args) { //String goatLatin = new LeetCode824().toGoatLatin("I speak Goat Latin"); - String goatLatin = new LeetCode824().toGoatLatin("The quick brown fox jumped over the lazy dog"); + String goatLatin = new LeetCode824_ToGoatLatin().toGoatLatin("The quick brown fox jumped over the lazy dog"); //String goatLatin = new LeetCode824().toGoatLatin("goat"); System.out.println(goatLatin); } @@ -38,7 +38,7 @@ public String toGoatLatin(String sentence) { // 元音开头 char[] chars = word.toCharArray(); if (chars[0] == 'A' || chars[0] == 'E' || chars[0] == 'I' || chars[0] == 'O' || chars[0] == 'U' || - chars[0] == 'a' || chars[0] == 'e' || chars[0] == 'i' || chars[0] == 'o' || chars[0] == 'u') { + chars[0] == 'a' || chars[0] == 'e' || chars[0] == 'i' || chars[0] == 'o' || chars[0] == 'u' ) { builder.append(word).append("ma"); } else { // 辅音开头 @@ -54,4 +54,26 @@ public String toGoatLatin(String sentence) { } return builder.toString(); } + + public String toGoatLatin2(String sentence) { + String[] array = sentence.split(" "); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + String word = array[i]; + // 元音开头 + char charAt0 = word.charAt(0); + if (charAt0 == 'A' || charAt0 == 'E' || charAt0 == 'I' || charAt0 == 'O' || charAt0 == 'U' || + charAt0 == 'a' || charAt0 == 'e' || charAt0 == 'i' || charAt0 == 'o' || charAt0 == 'u') { + builder.append(word).append("ma"); + } else { + // 辅音开头 + builder.append(word.substring(1)).append(charAt0).append("ma"); + } + for (int j = 0; j <= i; j++) { + builder.append("a"); + } + builder.append(" "); + } + return builder.toString().trim(); + } } From 7ed974eb8e14fa01ec290ccbfcf4164638cfa283 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 22 Apr 2022 13:36:33 +0800 Subject: [PATCH 022/105] docs: update README.md --- .gitignore | 2 ++ README.md | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6aa4da5..bb4ab1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ /target/ */systemlog/ !.mvn/wrapper/maven-wrapper.jar +*.class +*/*.class ### STS ### .apt_generated diff --git a/README.md b/README.md index 91266de..ead8918 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,7 @@ ## ☕ Java 新特性 Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错,何况有些新特性是**真香**。 +- [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) - [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) - [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) - [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/) From c881767bd754f675e89965dfa2aaa53506ad747c Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 24 Jun 2022 17:12:54 +0800 Subject: [PATCH 023/105] leetcode: 2022-06-24 LeetCode515_FindLargeValueInEachTreeRow --- ...etCode515_FindLargeValueInEachTreeRow.java | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java new file mode 100644 index 0000000..141a534 --- /dev/null +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java @@ -0,0 +1,76 @@ +package com.wdbyte.leetcode; + +import java.util.ArrayList; +import java.util.List; + +/** + * https://leetcode.cn/problems/find-largest-value-in-each-tree-row/ + * + * 515. 在每个树行中找最大值 + * + * @author niulang + * @date 2022/06/24 + */ +public class LeetCode515_FindLargeValueInEachTreeRow { + + public static void main(String[] args) { + LeetCode515_FindLargeValueInEachTreeRow code515 = new LeetCode515_FindLargeValueInEachTreeRow(); + TreeNode tree3_5 = new TreeNode(5); + TreeNode tree3_3 = new TreeNode(3); + TreeNode tree3_9 = new TreeNode(9); + TreeNode tree2_3 = new TreeNode(3, tree3_5, tree3_3); + TreeNode tree2_2 = new TreeNode(2, null, tree3_9); + TreeNode tree1_1 = new TreeNode(1, tree2_3, tree2_2); + List integers = code515.largestValues(tree1_1); + System.out.println(integers); + } + + public List largestValues(TreeNode root) { + List result = new ArrayList<>(); + if (root == null) { + return result; + } + List nodeList = new ArrayList<>(); + nodeList.add(root); + while (!nodeList.isEmpty()) { + List nodeListTemp = new ArrayList<>(); + Integer max = null; + for (TreeNode treeNode : nodeList) { + if (treeNode == null) { + continue; + } + if (max == null) { + max = treeNode.val; + } + if (max < treeNode.val) { + max = treeNode.val; + } + if (treeNode.left != null) { + nodeListTemp.add(treeNode.left); + } + if (treeNode.right != null) { + nodeListTemp.add(treeNode.right); + } + } + result.add(max); + nodeList = nodeListTemp; + } + return result; + } +} + +class TreeNode { + int val; + TreeNode left; + TreeNode right; + + TreeNode() {} + + TreeNode(int val) {this.val = val;} + + TreeNode(int val, TreeNode left, TreeNode right) { + this.val = val; + this.left = left; + this.right = right; + } +} \ No newline at end of file From 276700c3f3b81b5e52b2998e993cebcc9aed80f2 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sun, 10 Jul 2022 23:29:51 +0800 Subject: [PATCH 024/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0Java=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=AF=B9=E8=B1=A1=E6=B1=A0=E5=8C=96=20(https?= =?UTF-8?q?://www.wdbyte.com/java/object-pool.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + tool-java-object-pool/pom.xml | 41 +++++++++ .../wdbyet/tool/objectpool/JedisPoolTest.java | 22 +++++ .../apachekeyedpool/ApacheKeyedPool.java | 64 ++++++++++++++ .../objectpool/apachepool/ApachePool.java | 88 +++++++++++++++++++ .../tool/objectpool/mypool/MyObjectPool.java | 81 +++++++++++++++++ .../objectpool/mypool/MyObjectPoolTest.java | 58 ++++++++++++ 7 files changed, 355 insertions(+) create mode 100644 tool-java-object-pool/pom.xml create mode 100644 tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/JedisPoolTest.java create mode 100644 tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachekeyedpool/ApacheKeyedPool.java create mode 100644 tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachepool/ApachePool.java create mode 100644 tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPool.java create mode 100644 tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPoolTest.java diff --git a/pom.xml b/pom.xml index ff351cb..0decd9c 100644 --- a/pom.xml +++ b/pom.xml @@ -13,6 +13,7 @@ junit5-jupiter-starter apache-httpclient leetcode + tool-java-object-pool parent-modules Parent for all java modules diff --git a/tool-java-object-pool/pom.xml b/tool-java-object-pool/pom.xml new file mode 100644 index 0000000..f8c4630 --- /dev/null +++ b/tool-java-object-pool/pom.xml @@ -0,0 +1,41 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + objectpool + + + 8 + 8 + + + + + org.apache.commons + commons-pool2 + 2.11.1 + + + org.openjdk.jmh + jmh-core + 1.33 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.33 + provided + + + redis.clients + jedis + 4.2.0 + + + \ No newline at end of file diff --git a/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/JedisPoolTest.java b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/JedisPoolTest.java new file mode 100644 index 0000000..48b73b1 --- /dev/null +++ b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/JedisPoolTest.java @@ -0,0 +1,22 @@ +package com.wdbyet.tool.objectpool; + +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/09 + */ +public class JedisPoolTest { + + public static void main(String[] args) { + JedisPool jedisPool = new JedisPool("localhost", 6379); + // 从对象池中借一个对象 + Jedis jedis = jedisPool.getResource(); + String name = jedis.get("name"); + System.out.println("redis get :" + name); + jedis.close(); + // 彻底退出前,关闭 Redis 连接池 + jedisPool.close(); + } +} diff --git a/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachekeyedpool/ApacheKeyedPool.java b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachekeyedpool/ApacheKeyedPool.java new file mode 100644 index 0000000..8eed279 --- /dev/null +++ b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachekeyedpool/ApacheKeyedPool.java @@ -0,0 +1,64 @@ +package com.wdbyet.tool.objectpool.apachekeyedpool; + +import org.apache.commons.pool2.BaseKeyedPooledObjectFactory; +import org.apache.commons.pool2.KeyedPooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.impl.AbandonedConfig; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericKeyedObjectPool; +import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig; +import redis.clients.jedis.Jedis; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/07 + */ +public class ApacheKeyedPool { + + public static void main(String[] args) throws Exception { + String key = "local"; + MyGenericKeyedObjectPool objectMyObjectPool = new MyGenericKeyedObjectPool(new MyKeyedPooledObjectFactory()); + Jedis jedis = objectMyObjectPool.borrowObject(key); + String name = jedis.get("name"); + System.out.println("redis get :" + name); + objectMyObjectPool.returnObject(key, jedis); + } +} + +class MyKeyedPooledObjectFactory extends BaseKeyedPooledObjectFactory { + + @Override + public Jedis create(String key) throws Exception { + if ("local".equals(key)) { + return new Jedis("localhost", 6379); + } + if ("remote".equals(key)) { + return new Jedis("192.168.0.105", 6379); + } + return null; + } + + @Override + public PooledObject wrap(Jedis value) { + return new DefaultPooledObject<>(value); + } +} + +class MyGenericKeyedObjectPool extends GenericKeyedObjectPool { + + public MyGenericKeyedObjectPool(KeyedPooledObjectFactory factory) { + super(factory); + } + + public MyGenericKeyedObjectPool(KeyedPooledObjectFactory factory, + GenericKeyedObjectPoolConfig config) { + super(factory, config); + } + + public MyGenericKeyedObjectPool(KeyedPooledObjectFactory factory, + GenericKeyedObjectPoolConfig config, AbandonedConfig abandonedConfig) { + super(factory, config, abandonedConfig); + } +} + + diff --git a/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachepool/ApachePool.java b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachepool/ApachePool.java new file mode 100644 index 0000000..1ce04fd --- /dev/null +++ b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/apachepool/ApachePool.java @@ -0,0 +1,88 @@ +package com.wdbyet.tool.objectpool.apachepool; + +import org.apache.commons.pool2.BasePooledObjectFactory; +import org.apache.commons.pool2.PooledObject; +import org.apache.commons.pool2.PooledObjectFactory; +import org.apache.commons.pool2.impl.AbandonedConfig; +import org.apache.commons.pool2.impl.DefaultPooledObject; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import redis.clients.jedis.Jedis; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/07 + */ +public class ApachePool { + + public static void main(String[] args) throws Exception { + MyGenericObjectPool objectMyObjectPool = new MyGenericObjectPool(new MyPooledObjectFactory()); + Jedis jedis = objectMyObjectPool.borrowObject(); + String name = jedis.get("name"); + System.out.println("redis get:" + name); + objectMyObjectPool.returnObject(jedis); + objectMyObjectPool.close(); + } + +} + +class MyPooledObjectFactory implements PooledObjectFactory { + + @Override + public void activateObject(PooledObject pooledObject) throws Exception { + + } + + @Override + public void destroyObject(PooledObject pooledObject) throws Exception { + Jedis jedis = pooledObject.getObject(); + jedis.close(); + System.out.println("释放连接"); + } + + @Override + public PooledObject makeObject() throws Exception { + return new DefaultPooledObject(new Jedis("localhost", 6379)); + } + + @Override + public void passivateObject(PooledObject pooledObject) throws Exception { + + } + + @Override + public boolean validateObject(PooledObject pooledObject) { + return false; + } +} + +class SimplePooledObjectFactory extends BasePooledObjectFactory { + + @Override + public Jedis create() throws Exception { + return new Jedis("127.0.0.1", 6379); + } + + @Override + public PooledObject wrap(Jedis jedis) { + return new DefaultPooledObject<>(jedis); + } +} + +class MyGenericObjectPool extends GenericObjectPool { + + public MyGenericObjectPool(PooledObjectFactory factory) { + super(factory); + } + + public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config) { + super(factory, config); + } + + public MyGenericObjectPool(PooledObjectFactory factory, GenericObjectPoolConfig config, + AbandonedConfig abandonedConfig) { + super(factory, config, abandonedConfig); + } +} + + diff --git a/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPool.java b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPool.java new file mode 100644 index 0000000..124c8f2 --- /dev/null +++ b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPool.java @@ -0,0 +1,81 @@ +package com.wdbyet.tool.objectpool.mypool; + +import java.io.Closeable; +import java.io.IOException; +import java.util.HashSet; +import java.util.Stack; + +/** + * @author https://www.wdbyte.com + */ +public class MyObjectPool { + + // 池子大小 + private Integer size = 5; + // 对象池栈。后进先出 + private Stack stackPool = new Stack<>(); + // 借出的对象的 hashCode 集合 + private HashSet borrowHashCodeSet = new HashSet<>(); + + /** + * 增加一个对象 + * + * @param t + */ + public synchronized void addObj(T t) { + if ((stackPool.size() + borrowHashCodeSet.size()) == size) { + throw new RuntimeException("池中对象已经达到最大值"); + } + stackPool.add(t); + System.out.println("添加了对象:" + t.hashCode()); + } + + /** + * 借出一个对象 + * + * @return + */ + public synchronized T borrowObj() { + if (stackPool.isEmpty()) { + System.out.println("没有可以被借出的对象"); + return null; + } + T pop = stackPool.pop(); + borrowHashCodeSet.add(pop.hashCode()); + System.out.println("借出了对象:" + pop.hashCode()); + return pop; + } + + /** + * 归还一个对象 + * + * @param t + */ + public synchronized void returnObj(T t) { + if (borrowHashCodeSet.contains(t.hashCode())) { + stackPool.add(t); + borrowHashCodeSet.remove(t.hashCode()); + System.out.println("归还了对象:" + t.hashCode()); + return; + } + throw new RuntimeException("只能归还从池中借出的对象"); + } + + /** + * 销毁池中对象 + */ + public synchronized void destory() { + if (!borrowHashCodeSet.isEmpty()) { + throw new RuntimeException("尚有未归还的对象,不能关闭所有对象"); + } + while (!stackPool.isEmpty()) { + T pop = stackPool.pop(); + try { + pop.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + System.out.println("已经销毁了所有对象"); + } +} diff --git a/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPoolTest.java b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPoolTest.java new file mode 100644 index 0000000..3b29104 --- /dev/null +++ b/tool-java-object-pool/src/main/java/com/wdbyet/tool/objectpool/mypool/MyObjectPoolTest.java @@ -0,0 +1,58 @@ +package com.wdbyet.tool.objectpool.mypool; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import redis.clients.jedis.Jedis; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/02 + */ +@State(Scope.Benchmark) +@Warmup(iterations = 1, time = 3) +@Measurement(iterations = 3, time = 3) +public class MyObjectPoolTest { + private static MyObjectPool objectPool = new MyObjectPool<>(); + + static { + //objectPool.addObj(new Jedis("192.168.0.105", 6379)); + } + + public static void main(String[] args) { + MyObjectPool objectPool = new MyObjectPool<>(); + // 增加一个 jedis 连接对象 + objectPool.addObj(new Jedis("127.0.0.1", 6379)); + objectPool.addObj(new Jedis("127.0.0.1", 6379)); + // 从对象池中借出一个 jedis 对象 + Jedis jedis = objectPool.borrowObj(); + // 一次 redis 查询 + String name = jedis.get("name"); + System.out.println(String.format("redis get:" + name)); + // 归还 redis 连接对象 + objectPool.returnObj(jedis); + // 销毁对象池中的所有对象 + objectPool.destory(); + // 再次借用对象 + objectPool.borrowObj(); + } + + @Benchmark + public void testPool(Blackhole bh) { + Jedis jedis = objectPool.borrowObj(); + String name = jedis.get("name"); + objectPool.returnObj(jedis); + bh.consume(name); + } + + @Benchmark + public void test(Blackhole bh) { + Jedis jedis = new Jedis("localhost", 6379); + String name = jedis.get("name"); + jedis.close(); + bh.consume(name); + } +} From edc67f84c8e897885568f6cce2fe4a3d2966230d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sun, 10 Jul 2022 23:44:39 +0800 Subject: [PATCH 025/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0Java=20?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E5=AF=B9=E8=B1=A1=E6=B1=A0=E5=8C=96=20(https?= =?UTF-8?q?://www.wdbyte.com/java/object-pool.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index ead8918..c70dec4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ > **原创文章每周更新**。技术文字在写的过程中难免会有纰漏,或者细节不够完善。大家发现问题,可以及时给我 PR 反馈,也可以去 [公众号](https://github.com/niumoo/JavaNotes#%E5%85%AC%E4%BC%97%E5%8F%B7) 给我留言,或者加我 [微信](https://github.com/niumoo/JavaNotes#联系我) 直接说明,我都会及时更正,哪怕是一个错别字。加油!奥利给! - -

Java Notes

wechat @@ -19,6 +17,7 @@ ## ⏳ Java 开发 +- [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html) - [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) - [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) From 63a6b60be82b57b903a46ceddcde98370d62e329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Thu, 14 Jul 2022 23:17:26 +0800 Subject: [PATCH 026/105] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c70dec4..4906a79 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ ## ⏳ Java 开发 +- [必应壁纸,我的第一个 400 Star 开源项目](https://www.wdbyte.com/bing-wallpaper-400.html) - [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html) - [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) - [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) From 7b1415ceebdf917c8027be9687b626d88572a452 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 21 Jul 2022 00:01:07 +0800 Subject: [PATCH 027/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Jackson=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=93=8D=E4=BD=9C(https://www.wdbyte.com/too?= =?UTF-8?q?l/jackson.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tool-java-jackson/README.md | 4 + tool-java-jackson/pom.xml | 42 ++++++++ tool-java-jackson/src/EmployeeList.json | 18 ++++ tool-java-jackson/src/Person.json | 8 ++ .../src/main/java/com/wdbyte/jackson/Cat.java | 24 +++++ .../main/java/com/wdbyte/jackson/Order.java | 57 ++++++++++ .../main/java/com/wdbyte/jackson/Person.java | 17 +++ .../main/java/com/wdbyte/jackson/Student.java | 48 +++++++++ .../test/java/com/wdbyte/jackson/CatTest.java | 47 ++++++++ .../java/com/wdbyte/jackson/OrderTest.java | 44 ++++++++ .../java/com/wdbyte/jackson/PersonTest.java | 100 ++++++++++++++++++ .../java/com/wdbyte/jackson/PersonTest2.java | 24 +++++ .../java/com/wdbyte/jackson/StudentTest.java | 46 ++++++++ 13 files changed, 479 insertions(+) create mode 100644 tool-java-jackson/README.md create mode 100644 tool-java-jackson/pom.xml create mode 100644 tool-java-jackson/src/EmployeeList.json create mode 100644 tool-java-jackson/src/Person.json create mode 100644 tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java create mode 100644 tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java create mode 100644 tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java create mode 100644 tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java create mode 100644 tool-java-jackson/src/test/java/com/wdbyte/jackson/CatTest.java create mode 100644 tool-java-jackson/src/test/java/com/wdbyte/jackson/OrderTest.java create mode 100644 tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest.java create mode 100644 tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest2.java create mode 100644 tool-java-jackson/src/test/java/com/wdbyte/jackson/StudentTest.java diff --git a/tool-java-jackson/README.md b/tool-java-jackson/README.md new file mode 100644 index 0000000..0b9e3f0 --- /dev/null +++ b/tool-java-jackson/README.md @@ -0,0 +1,4 @@ +## tool-java-jackson + +### 相关文章 +- [Jackson 解析 JSON 教程](https://www.wdbyte.com/tool/jackson.html) diff --git a/tool-java-jackson/pom.xml b/tool-java-jackson/pom.xml new file mode 100644 index 0000000..51e295d --- /dev/null +++ b/tool-java-jackson/pom.xml @@ -0,0 +1,42 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.jackson + tool-java-jackson + + + 8 + 8 + + + + + com.fasterxml.jackson.core + jackson-databind + 2.13.3 + + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.13.3 + + + + org.junit.jupiter + junit-jupiter + 5.8.2 + test + + + + + \ No newline at end of file diff --git a/tool-java-jackson/src/EmployeeList.json b/tool-java-jackson/src/EmployeeList.json new file mode 100644 index 0000000..ab00d44 --- /dev/null +++ b/tool-java-jackson/src/EmployeeList.json @@ -0,0 +1,18 @@ +[ + { + "name": "aLang", + "age": 27, + "skillList": [ + "java", + "c++" + ] + }, + { + "name": "darcy", + "age": 26, + "skillList": [ + "go", + "rust" + ] + } +] \ No newline at end of file diff --git a/tool-java-jackson/src/Person.json b/tool-java-jackson/src/Person.json new file mode 100644 index 0000000..2d2e72b --- /dev/null +++ b/tool-java-jackson/src/Person.json @@ -0,0 +1,8 @@ +{ + "name": "aLang", + "age": 27, + "skillList": [ + "java", + "c++" + ] +} \ No newline at end of file diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java new file mode 100644 index 0000000..5492a46 --- /dev/null +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java @@ -0,0 +1,24 @@ +package com.wdbyte.jackson; + +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Data; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +@Data +public class Cat { + + @JsonSetter(value = "catName") + private String name; + + private Integer age; + + @JsonGetter(value = "catName") + public String getName() { + return name; + } +} diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java new file mode 100644 index 0000000..6318eb3 --- /dev/null +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java @@ -0,0 +1,57 @@ +package com.wdbyte.jackson; + +import java.time.LocalDateTime; +import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +//@Data +@AllArgsConstructor +@NoArgsConstructor +@ToString +public class Order { + + @JsonSetter(value = "orderId") + private Integer id; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private Date createTime; + + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") + private LocalDateTime updateTime; + + //@JsonGetter(value = "orderId") + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public LocalDateTime getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(LocalDateTime updateTime) { + this.updateTime = updateTime; + } +} diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java new file mode 100644 index 0000000..3bc1210 --- /dev/null +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java @@ -0,0 +1,17 @@ +package com.wdbyte.jackson; + +import java.util.List; + +import lombok.Data; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/16 + */ +@Data +public class Person { + private String name; + private Integer age; + private List skillList; +} + diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java new file mode 100644 index 0000000..377253d --- /dev/null +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java @@ -0,0 +1,48 @@ +package com.wdbyte.jackson; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.google.common.collect.Maps; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class Student { + @Getter + @Setter + private String name; + + @Getter + @Setter + private Integer age; + + @Getter + @Setter + private Map diyMap = new HashMap<>(); + + @JsonAnyGetter + private Map initMap = new HashMap() {{ + put("a", 111); + put("b", 222); + put("c", 333); + }}; + + @JsonAnySetter + public void otherField(String key, String value) { + this.diyMap.put(key, value); + } + +} diff --git a/tool-java-jackson/src/test/java/com/wdbyte/jackson/CatTest.java b/tool-java-jackson/src/test/java/com/wdbyte/jackson/CatTest.java new file mode 100644 index 0000000..d2b82ba --- /dev/null +++ b/tool-java-jackson/src/test/java/com/wdbyte/jackson/CatTest.java @@ -0,0 +1,47 @@ +package com.wdbyte.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +class CatTest { + + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testPojoToJson() throws JsonProcessingException { + Cat cat = new Cat(); + cat.setName("Tom"); + cat.setAge(2); + String json = objectMapper.writeValueAsString(cat); + System.out.println(json); + + Assertions.assertEquals(json, "{\"name\":\"Tom\"}"); + + cat = objectMapper.readValue(json, Cat.class); + Assertions.assertEquals(cat.getName(), "Tom"); + Assertions.assertEquals(cat.getAge(), null); + } + + @Test + void testJsonToPojo() throws JsonProcessingException { + String json = "{\"name\":\"tom\"}"; + Cat cat = objectMapper.readValue(json, Cat.class); + Assertions.assertEquals(cat.getName(), "Tom"); + Assertions.assertEquals(cat.getAge(), null); + } + + @Test + void testPojoToJson2() throws JsonProcessingException { + String json = "{\"age\":2,\"catName\":\"Tom\"}"; + Cat cat = objectMapper.readValue(json, Cat.class); + System.out.println(cat.toString()); + Assertions.assertEquals(cat.getName(), "Tom"); + } + +} \ No newline at end of file diff --git a/tool-java-jackson/src/test/java/com/wdbyte/jackson/OrderTest.java b/tool-java-jackson/src/test/java/com/wdbyte/jackson/OrderTest.java new file mode 100644 index 0000000..8cd5a11 --- /dev/null +++ b/tool-java-jackson/src/test/java/com/wdbyte/jackson/OrderTest.java @@ -0,0 +1,44 @@ +package com.wdbyte.jackson; + +import java.time.LocalDateTime; +import java.util.Date; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +class OrderTest { + + ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); + //ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testPojoToJson() throws JsonProcessingException { + Order order = new Order(1, new Date(), LocalDateTime.now()); + String json = objectMapper.writeValueAsString(order); + System.out.println(json); + + order = objectMapper.readValue(json, Order.class); + System.out.println(order.toString()); + + Assertions.assertEquals(order.getId(), 1); + } + + @Test + void testPojoToJson0() throws JsonProcessingException { + Order order = new Order(1, new Date(), null); + String json = objectMapper.writeValueAsString(order); + System.out.println(json); + + order = objectMapper.readValue(json, Order.class); + System.out.println( order.toString()); + + Assertions.assertEquals(order.getId(), 1); + } + +} \ No newline at end of file diff --git a/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest.java b/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest.java new file mode 100644 index 0000000..dcfff39 --- /dev/null +++ b/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest.java @@ -0,0 +1,100 @@ +package com.wdbyte.jackson; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/16 + */ +class PersonTest { + + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void pojoToJsonString() throws JsonProcessingException { + Person person = new Person(); + person.setName("aLng"); + person.setAge(27); + person.setSkillList(Arrays.asList("java", "c++")); + + String json = objectMapper.writeValueAsString(person); + System.out.println(json); + String expectedJson = "{\"name\":\"aLng\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + Assertions.assertEquals(json, expectedJson); + } + + @Test + void jsonStringToPojo() throws JsonProcessingException { + String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + Person person = objectMapper.readValue(expectedJson, Person.class); + System.out.println(person); + Assertions.assertEquals(person.getName(), "aLang"); + Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); + } + + @Test + void testJsonFilePojo() throws IOException { + File file = new File("src/Person.json"); + Person person = objectMapper.readValue(file, Person.class); + System.out.println(person); + Assertions.assertEquals(person.getName(), "aLang"); + Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); + } + + @Test + void jsonBytesToPojo() throws IOException { + String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + Person person = objectMapper.readValue(expectedJson.getBytes(), Person.class); + System.out.println(person); + Assertions.assertEquals(person.getName(), "aLang"); + Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); + } + + @Test + void fileToPojoList() throws IOException { + File file = new File("src/EmployeeList.json"); + List personList = objectMapper.readValue(file, new TypeReference>() {}); + for (Person person : personList) { + System.out.println(person); + } + Assertions.assertEquals(personList.size(), 2); + Assertions.assertEquals(personList.get(0).getName(), "aLang"); + Assertions.assertEquals(personList.get(1).getName(), "darcy"); + } + + @Test + void jsonStringToMap() throws IOException { + String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + Map employeeMap = objectMapper.readValue(expectedJson, new TypeReference() {}); + System.out.println(employeeMap.getClass()); + for (Entry entry : employeeMap.entrySet()) { + System.out.println(entry.getKey() + ":" + entry.getValue()); + } + Assertions.assertEquals(employeeMap.get("name"), "aLang"); + } + + + @Test + void jsonStringToPojoIgnoreProperties() throws IOException { + // UnrecognizedPropertyException + String json = "{\"yyy\":\"xxx\",\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + Person person = objectMapper.readValue(json, Person.class); + System.out.printf(person.toString()); + Assertions.assertEquals(person.getName(), "aLang"); + Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); + } + +} \ No newline at end of file diff --git a/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest2.java b/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest2.java new file mode 100644 index 0000000..a89200e --- /dev/null +++ b/tool-java-jackson/src/test/java/com/wdbyte/jackson/PersonTest2.java @@ -0,0 +1,24 @@ +package com.wdbyte.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/16 + */ +class PersonTest2 { + + ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void jsonStringToPojo() throws JsonProcessingException { + String expectedJson = "{\"name\":\"aLang\",\"age\":27,\"skillList\":[\"java\",\"c++\"]}"; + Person person = objectMapper.readValue(expectedJson, Person.class); + System.out.println(person); + Assertions.assertEquals(person.getName(), "aLang"); + Assertions.assertEquals(person.getSkillList().toString(), "[java, c++]"); + } +} \ No newline at end of file diff --git a/tool-java-jackson/src/test/java/com/wdbyte/jackson/StudentTest.java b/tool-java-jackson/src/test/java/com/wdbyte/jackson/StudentTest.java new file mode 100644 index 0000000..3c4d327 --- /dev/null +++ b/tool-java-jackson/src/test/java/com/wdbyte/jackson/StudentTest.java @@ -0,0 +1,46 @@ +package com.wdbyte.jackson; + +import java.util.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * @author https://www.wdbyte.com + * @date 2022/07/17 + */ +class StudentTest { + + private ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void testJsonToPojo() throws JsonProcessingException { + Map map = new HashMap<>(); + map.put("name", "aLang"); + map.put("age", 18); + map.put("skill", "java"); + + String json = objectMapper.writeValueAsString(map); + System.out.println(json); + + Student student = objectMapper.readValue(json, Student.class); + System.out.println(student); + + Assertions.assertEquals(student.getDiyMap().get("skill"), "java"); + } + + @Test + void testPojoToJsonTest() throws JsonProcessingException { + Student student = new Student(); + student.setName("aLang"); + student.setAge(20); + String json = objectMapper.writeValueAsString(student); + System.out.println(json); + + Assertions.assertEquals(json,"{\"name\":\"aLang\",\"age\":20,\"diyMap\":{},\"a\":111,\"b\":222,\"c\":333}"); + } + +} \ No newline at end of file From 156336cd51243410052529324523764164934add Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 21 Jul 2022 00:02:20 +0800 Subject: [PATCH 028/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Jackson=20?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E6=93=8D=E4=BD=9C(https://www.wdbyte.com/too?= =?UTF-8?q?l/jackson.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- pom.xml | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4906a79..c74a0ed 100644 --- a/README.md +++ b/README.md @@ -165,7 +165,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 一款好用的工具,不仅可以装X,更可以让你事半功倍,准时下班。 - +- [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) - [Java 反编译工具的使用与对比分析](https://www.wdbyte.com/2021/05/java-decompiler/) - [可以Postman,也可以cURL.进来领略下cURL的独门绝技](https://www.wdbyte.com/2020/06/tool/curl/) - [抛弃Eclipse,投入IDEA 的独孤求败江湖](https://www.wdbyte.com/2019/10/develop/idea-skill/) diff --git a/pom.xml b/pom.xml index 0decd9c..f2874bf 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,7 @@ apache-httpclient leetcode tool-java-object-pool + tool-java-jackson parent-modules Parent for all java modules @@ -23,6 +24,7 @@ 1.8 31.0.1-jre 3.12.0 + 1.18.22 @@ -37,5 +39,10 @@ commons-lang3 ${commons-lang3.version} + + org.projectlombok + lombok + ${lombok.version} + From 4b7a76c02c220fc9c648c0a88a9449b74bf876ff Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 5 Aug 2022 15:33:53 +0800 Subject: [PATCH 029/105] build: add apache-httpclient --- pom.xml | 2 +- .../.gitignore | 0 .../pom.xml | 13 ++++--------- 3 files changed, 5 insertions(+), 10 deletions(-) rename {apache-httpclient => tool-java-apache-httpclient}/.gitignore (100%) rename {apache-httpclient => tool-java-apache-httpclient}/pom.xml (82%) diff --git a/pom.xml b/pom.xml index f2874bf..0fe1bcd 100644 --- a/pom.xml +++ b/pom.xml @@ -11,8 +11,8 @@ core-java-modules core-java-rate-limiter junit5-jupiter-starter - apache-httpclient leetcode + tool-java-apache-httpclient tool-java-object-pool tool-java-jackson diff --git a/apache-httpclient/.gitignore b/tool-java-apache-httpclient/.gitignore similarity index 100% rename from apache-httpclient/.gitignore rename to tool-java-apache-httpclient/.gitignore diff --git a/apache-httpclient/pom.xml b/tool-java-apache-httpclient/pom.xml similarity index 82% rename from apache-httpclient/pom.xml rename to tool-java-apache-httpclient/pom.xml index 36e0086..c78012a 100644 --- a/apache-httpclient/pom.xml +++ b/tool-java-apache-httpclient/pom.xml @@ -10,27 +10,22 @@ 4.0.0 com.wdbyte.httpclient - apache-httpclient + tool-java-apache-httpclient - 17 - 17 + 1.8 + 1.8 + org.apache.httpcomponents.client5 httpclient5 5.1.3 - - org.junit.jupiter - junit-jupiter - test - - org.apache.httpcomponents.client5 From 4dee6b0ba27165531d72ddfe8155b86e9d759617 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 5 Aug 2022 15:35:09 +0800 Subject: [PATCH 030/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20HttpClient?= =?UTF-8?q?5=20GET=20=E8=AF=B7=E6=B1=82(https://www.wdbyte.com/httpclient5?= =?UTF-8?q?/get.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wdbyte/httpclient/HttpClient5Get.java | 43 ++++++++++++ .../httpclient/HttpClient5GetFluent.java | 28 ++++++++ .../httpclient/HttpClient5GetParams.java | 65 +++++++++++++++++++ 3 files changed, 136 insertions(+) create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Get.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetFluent.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetParams.java diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Get.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Get.java new file mode 100644 index 0000000..136b61b --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Get.java @@ -0,0 +1,43 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** +* @author https://www.wdbyte.com + * @date 2022/04/01 + */ +public class HttpClient5Get { + + public static void main(String[] args) { + String result = get("http://httpbin.org/get"); + System.out.println(result); + } + + public static String get(String url) { + String resultContent = null; + HttpGet httpGet = new HttpGet(url); + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + try (CloseableHttpResponse response = httpclient.execute(httpGet)) { + // 获取状态码 + System.out.println(response.getVersion()); // HTTP/1.1 + System.out.println(response.getCode()); // 200 + System.out.println(response.getReasonPhrase()); // OK + HttpEntity entity = response.getEntity(); + // 获取响应信息 + resultContent = EntityUtils.toString(entity); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return resultContent; + } + +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetFluent.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetFluent.java new file mode 100644 index 0000000..76b1077 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetFluent.java @@ -0,0 +1,28 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; + +import org.apache.hc.client5.http.fluent.Request; +import org.apache.hc.client5.http.fluent.Response; + +/** +* @author https://www.wdbyte.com + */ +public class HttpClient5GetFluent { + + public static void main(String[] args) { + System.out.println(get("http://httpbin.org/get")); + } + + public static String get(String url) { + String result = null; + try { + Response response = Request.get(url).execute(); + result = response.returnContent().asString(); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + } + +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetParams.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetParams.java new file mode 100644 index 0000000..868f149 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetParams.java @@ -0,0 +1,65 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicNameValuePair; +import org.apache.hc.core5.net.URIBuilder; + +/** +* @author https://www.wdbyte.com + * @date 2022/04/01 + */ +public class HttpClient5GetParams { + + public static void main(String[] args) { + String result = get("http://httpbin.org/get"); + System.out.println(result); + } + + public static String get(String url) { + String resultContent = null; + HttpGet httpGet = new HttpGet(url); + // 表单参数 + List nvps = new ArrayList<>(); + // GET 请求参数 + nvps.add(new BasicNameValuePair("username", "wdbyte.com")); + nvps.add(new BasicNameValuePair("password", "secret")); + // 增加到请求 URL 中 + try { + URI uri = new URIBuilder(new URI(url)) + .addParameters(nvps) + .build(); + httpGet.setUri(uri); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + try (CloseableHttpResponse response = httpclient.execute(httpGet)) { + // 获取状态码 + System.out.println(response.getVersion()); // HTTP/1.1 + System.out.println(response.getCode()); // 200 + System.out.println(response.getReasonPhrase()); // OK + HttpEntity entity = response.getEntity(); + // 获取响应信息 + resultContent = EntityUtils.toString(entity); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return resultContent; + } + +} From f0d82dcf4f21492ccaec303a55006dffff0b91da Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 5 Aug 2022 15:35:30 +0800 Subject: [PATCH 031/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20HttpClient?= =?UTF-8?q?5=20POST=20=E8=AF=B7=E6=B1=82(https://www.wdbyte.com/httpclient?= =?UTF-8?q?5/post.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wdbyte/httpclient/HttpClient5Post.java | 77 +++++++++++++++++++ .../httpclient/HttpClient5PostFluent.java | 33 ++++++++ .../httpclient/HttpClient5PostWithJson.java | 68 ++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Post.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostFluent.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostWithJson.java diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Post.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Post.java new file mode 100644 index 0000000..713b191 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Post.java @@ -0,0 +1,77 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicNameValuePair; + +/** +* @author https://www.wdbyte.com + */ +public class HttpClient5Post { + + public static void main(String[] args) { + String result = post("http://httpbin.org/post"); + System.out.println(result); + } + + /** + * { + * "args": {}, + * "data": "", + * "files": {}, + * "form": { + * "password": "secret", + * "username": "wdbyte.com" + * }, + * "headers": { + * "Accept-Encoding": "gzip, x-gzip, deflate", + * "Content-Length": "31", + * "Content-Type": "application/x-www-form-urlencoded; charset=ISO-8859-1", + * "Host": "httpbin.org", + * "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)", + * "X-Amzn-Trace-Id": "Root=1-62ab2708-4425003d3641a0a75cfeda74" + * }, + * "json": null, + * "origin": "47.251.4.198", + * "url": "http://httpbin.org/post" + * } + */ + public static String post(String url) { + String result = null; + HttpPost httpPost = new HttpPost(url); + // 表单参数 + List nvps = new ArrayList<>(); + // POST 请求参数 + nvps.add(new BasicNameValuePair("username", "wdbyte.com")); + nvps.add(new BasicNameValuePair("password", "secret")); + httpPost.setEntity(new UrlEncodedFormEntity(nvps)); + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + System.out.println(response.getVersion()); // HTTP/1.1 + System.out.println(response.getCode()); // 200 + System.out.println(response.getReasonPhrase()); // OK + + HttpEntity entity = response.getEntity(); + // 获取响应信息 + result = EntityUtils.toString(entity); + // 确保流被完全消费 + EntityUtils.consume(entity); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return result; + } + +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostFluent.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostFluent.java new file mode 100644 index 0000000..4053cc8 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostFluent.java @@ -0,0 +1,33 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; + +import org.apache.hc.client5.http.fluent.Request; +import org.apache.hc.core5.http.message.BasicNameValuePair; + +/** +* @author https://www.wdbyte.com + */ +public class HttpClient5PostFluent { + + public static void main(String[] args) { + String result = post("http://httpbin.org/post"); + System.out.println(result); + } + + public static String post(String url) { + String result = null; + Request request = Request.post(url); + // POST 请求参数 + request.bodyForm( + new BasicNameValuePair("username", "wdbyte.com"), + new BasicNameValuePair("password", "secret")); + try { + result = request.execute().returnContent().asString(); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + } + +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostWithJson.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostWithJson.java new file mode 100644 index 0000000..921c64f --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PostWithJson.java @@ -0,0 +1,68 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +/** + * @author https://www.wdbyte.com + */ +public class HttpClient5PostWithJson { + + /** + * { + * "args": {}, + * "data": "{ \"password\": \"secret\", \"username\": \"wdbyte.com\"}", + * "files": {}, + * "form": {}, + * "headers": { + * "Accept-Encoding": "gzip, x-gzip, deflate", + * "Content-Length": "55", + * "Content-Type": "text/plain; charset=ISO-8859-1", + * "Host": "httpbin.org", + * "User-Agent": "Apache-HttpClient/5.1.3 (Java/17)", + * "X-Amzn-Trace-Id": "Root=1-62b6ab18-2aec3a5731e325620f1f5717" + * }, + * "json": { + * "password": "secret", + * "username": "wdbyte.com" + * }, + * "origin": "42.120.74.8", + * "url": "http://httpbin.org/post" + * } + * + * @param args + */ + public static void main(String[] args) { + String json = "{" + + " \"password\": \"secret\"," + + " \"username\": \"wdbyte.com\"" + + "}"; + String result = post("http://httpbin.org/post", json); + System.out.println(result); + } + + public static String post(String url, String jsonBody) { + String result = null; + HttpPost httpPost = new HttpPost(url); + httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON)); + + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + // 获取响应信息 + result = EntityUtils.toString(response.getEntity()); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return result; + } + +} From 3092e34c436c86ce9c7bf08680c72dbe75f3e468 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 17 Aug 2022 09:22:34 +0800 Subject: [PATCH 032/105] =?UTF-8?q?feat:=20Apache=20HttpClient=205=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E8=AF=A6=E7=BB=86=E6=95=99=E7=A8=8B(https://?= =?UTF-8?q?www.wdbyte.com/tool/httpclient5.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- tool-java-apache-httpclient/README.md | 5 + .../wdbyte/httpclient/HttpClient5Async.java | 172 ++++++++++++++++++ .../HttpClient5BasicAuthentication.java | 72 ++++++++ .../httpclient/HttpClient5CancleMethod.java | 27 +++ .../HttpClient5ChunkEncodedPost.java | 42 +++++ .../httpclient/HttpClient5CustomSSL.java | 76 ++++++++ .../httpclient/HttpClient5ExecuteProxy.java | 46 +++++ .../httpclient/HttpClient5FormLogin.java | 58 ++++++ .../HttpClient5GetWithBasicAuth.java | 51 ++++++ .../httpclient/HttpClient5GetWithTimeout.java | 54 ++++++ .../httpclient/HttpClient5Interceptors.java | 81 +++++++++ ...pClient5PreemptiveBasicAuthentication.java | 59 ++++++ ...Client5PreemptiveDigestAuthentication.java | 104 +++++++++++ .../HttpClient5ProxyAuthentication.java | 48 +++++ .../HttpClient5ProxyTunnelDemo.java | 42 +++++ .../httpclient/HttpClient5WithCookie.java | 53 ++++++ 17 files changed, 991 insertions(+), 1 deletion(-) create mode 100644 tool-java-apache-httpclient/README.md create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Async.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5BasicAuthentication.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CancleMethod.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ChunkEncodedPost.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CustomSSL.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ExecuteProxy.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5FormLogin.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithBasicAuth.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithTimeout.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Interceptors.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveBasicAuthentication.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveDigestAuthentication.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyAuthentication.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyTunnelDemo.java create mode 100644 tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5WithCookie.java diff --git a/README.md b/README.md index c74a0ed..26d8a9e 100644 --- a/README.md +++ b/README.md @@ -164,7 +164,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 >出处:孔子《论语》 一款好用的工具,不仅可以装X,更可以让你事半功倍,准时下班。 - +- [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html) - [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) - [Java 反编译工具的使用与对比分析](https://www.wdbyte.com/2021/05/java-decompiler/) - [可以Postman,也可以cURL.进来领略下cURL的独门绝技](https://www.wdbyte.com/2020/06/tool/curl/) diff --git a/tool-java-apache-httpclient/README.md b/tool-java-apache-httpclient/README.md new file mode 100644 index 0000000..cbbdd52 --- /dev/null +++ b/tool-java-apache-httpclient/README.md @@ -0,0 +1,5 @@ +## tool-java-apache-httpclient +当前模块包含 Apache HttpClient 5 相关代码 + +### 相关文章 +- [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html) diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Async.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Async.java new file mode 100644 index 0000000..ae325f8 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Async.java @@ -0,0 +1,172 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; +import java.nio.CharBuffer; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.async.methods.AbstractCharResponseConsumer; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; +import org.apache.hc.client5.http.async.methods.SimpleHttpRequests; +import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClients; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpResponse; +import org.apache.hc.core5.http.nio.AsyncRequestProducer; +import org.apache.hc.core5.http.nio.support.AsyncRequestBuilder; + +/** + * HttpClient 5 异步请求 +* @author https://www.wdbyte.com + * @date 2022/06/25 + */ +public class HttpClient5Async { + + public static void main(String[] args) { + getAsync1("http://httpbin.org/get"); + getAsync2("http://httpbin.org/get"); + getAsync3("http://httpbin.org/get"); + } + + /** + * 异步请求 + * + * @param url + * @return + */ + public static String getAsync1(String url) { + try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) { + // 开始 http clinet + httpclient.start(); + // 执行请求 + SimpleHttpRequest request1 = SimpleHttpRequests.get(url); + Future future = httpclient.execute(request1, null); + // 等待直到返回完毕 + SimpleHttpResponse response1 = future.get(); + System.out.println("getAsync1:" + request1.getRequestUri() + "->" + response1.getCode()); + } catch (IOException | ExecutionException | InterruptedException e) { + throw new RuntimeException(e); + } + return null; + } + + /** + * 异步请求,根据响应情况回调 + * + * @param url + * @return + */ + public static String getAsync2(String url) { + try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) { + // 开始 http clinet + httpclient.start(); + // 根据请求响应情况进行回调操作 + CountDownLatch latch = new CountDownLatch(1); + SimpleHttpRequest request = SimpleHttpRequests.get(url); + httpclient.execute(request, new FutureCallback() { + @Override + public void completed(SimpleHttpResponse response2) { + latch.countDown(); + System.out.println("getAsync2:" + request.getRequestUri() + "->" + response2.getCode()); + } + + @Override + public void failed(Exception ex) { + latch.countDown(); + System.out.println("getAsync2:" + request.getRequestUri() + "->" + ex); + } + + @Override + public void cancelled() { + latch.countDown(); + System.out.println("getAsync2:" + request.getRequestUri() + " cancelled"); + } + + }); + latch.await(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + return null; + } + + /** + * 异步请求,对响应流做点什么 + * + * @param url + * @return + */ + public static String getAsync3(String url) { + try (CloseableHttpAsyncClient httpclient = HttpAsyncClients.createDefault()) { + // 开始 http clinet + httpclient.start(); + // 根据请求响应情况进行回调操作 + SimpleHttpRequest request = SimpleHttpRequests.get(url); + + CountDownLatch latch = new CountDownLatch(1); + AsyncRequestProducer producer = AsyncRequestBuilder.get("http://httpbin.org/get").build(); + AbstractCharResponseConsumer consumer3 = new AbstractCharResponseConsumer() { + + HttpResponse response; + + @Override + protected void start(HttpResponse response, ContentType contentType) throws HttpException, IOException { + System.out.println("getAsync3: 开始响应...."); + this.response = response; + } + + @Override + protected int capacityIncrement() { + return Integer.MAX_VALUE; + } + + @Override + protected void data(CharBuffer data, boolean endOfStream) throws IOException { + System.out.println("getAsync3: 收到数据...."); + // Do something useful + } + + @Override + protected HttpResponse buildResult() throws IOException { + System.out.println("getAsync3: 接收完毕..."); + return response; + } + + @Override + public void releaseResources() { + } + + }; + httpclient.execute(producer, consumer3, new FutureCallback() { + + @Override + public void completed(HttpResponse response) { + latch.countDown(); + System.out.println("getAsync3: "+request.getRequestUri() + "->" + response.getCode()); + } + + @Override + public void failed(Exception ex) { + latch.countDown(); + System.out.println("getAsync3: "+request.getRequestUri() + "->" + ex); + } + + @Override + public void cancelled() { + latch.countDown(); + System.out.println("getAsync3: "+request.getRequestUri() + " cancelled"); + } + + }); + latch.await(); + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + return null; + + } +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5BasicAuthentication.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5BasicAuthentication.java new file mode 100644 index 0000000..0ccf076 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5BasicAuthentication.java @@ -0,0 +1,72 @@ +package com.wdbyte.httpclient; + +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * 一个简单的示例,它使用HttpClient执行HTTP请求; + * 一个需要进行用户身份验证的目标站点。 + * Basic Authorization + * + * GET /basic-auth/user/passwd HTTP/1.1 + * Accept-Encoding: gzip, x-gzip, deflate + * Host: httpbin.org + * Connection: keep-alive + * User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151) + * + * HTTP/1.1 401 UNAUTHORIZED + * Date: Sat, 06 Aug 2022 08:25:33 GMT + * Content-Length: 0 + * Connection: keep-alive + * Server: gunicorn/19.9.0 + * WWW-Authenticate: Basic realm="Fake Realm" + * Access-Control-Allow-Origin: * + * Access-Control-Allow-Credentials: true + * + * GET /basic-auth/user/passwd HTTP/1.1 + * Host: httpbin.org + * Connection: keep-alive + * User-Agent: Apache-HttpClient/5.1.3 (Java/1.8.0_151) + * Authorization: Basic dXNlcjpwYXNzd2Q= + * + * HTTP/1.1 200 OK + * Date: Sat, 06 Aug 2022 08:25:33 GMT + * Content-Type: application/json + * Content-Length: 47 + * Connection: keep-alive + * Server: gunicorn/19.9.0 + * Access-Control-Allow-Origin: * + * Access-Control-Allow-Credentials: true + * + * { + * "authenticated": true, + * "user": "user" + * } + */ +public class HttpClient5BasicAuthentication { + + public static void main(final String[] args) throws Exception { + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope("httpbin.org", 80), + new UsernamePasswordCredentials("admin", "123456".toCharArray())); + try (final CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCredentialsProvider(credsProvider) + .build()) { + final HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/admin/123456"); + + System.out.println("执行请求" + httpget.getMethod() + " " + httpget.getUri()); + try (final CloseableHttpResponse response = httpclient.execute(httpget)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } + } +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CancleMethod.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CancleMethod.java new file mode 100644 index 0000000..11ed77b --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CancleMethod.java @@ -0,0 +1,27 @@ +package com.wdbyte.httpclient; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; + +/** + * 这个案例展示了如何中止一个HTTP方法之前正常完成。; + */ +public class HttpClient5CancleMethod { + + public static void main(final String[] args) throws Exception { + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + final HttpGet httpget = new HttpGet("http://httpbin.org/get"); + + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + try (final CloseableHttpResponse response = httpclient.execute(httpget)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + // 不像读取结果,直接中止 + httpget.cancel(); + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ChunkEncodedPost.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ChunkEncodedPost.java new file mode 100644 index 0000000..e587844 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ChunkEncodedPost.java @@ -0,0 +1,42 @@ +package com.wdbyte.httpclient; + +import java.io.File; +import java.io.FileInputStream; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.FileEntity; +import org.apache.hc.core5.http.io.entity.InputStreamEntity; + +/** + * 加载数据流作为 POST 请求参数 + */ +public class HttpClient5ChunkEncodedPost { + + public static void main(final String[] args) throws Exception { + String params = "/Users/darcy/params.json"; + + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + final HttpPost httppost = new HttpPost("http://httpbin.org/post"); + + final InputStreamEntity reqEntity = new InputStreamEntity(new FileInputStream(params), -1, + ContentType.APPLICATION_JSON); + // 也可以使用 FileEntity 的形式 + // FileEntity reqEntity = new FileEntity(new File(params), ContentType.APPLICATION_JSON); + + httppost.setEntity(reqEntity); + + System.out.println("执行请求 " + httppost.getMethod() + " " + httppost.getUri()); + try (final CloseableHttpResponse response = httpclient.execute(httppost)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CustomSSL.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CustomSSL.java new file mode 100644 index 0000000..90064b6 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5CustomSSL.java @@ -0,0 +1,76 @@ +package com.wdbyte.httpclient; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactoryBuilder; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.ssl.TLS; +import org.apache.hc.core5.ssl.SSLContexts; +import org.apache.hc.core5.ssl.TrustStrategy; + +/** + * This example demonstrates how to create secure connections with a custom SSL + * context. + */ +public class HttpClient5CustomSSL { + + public final static void main(final String[] args) throws Exception { + // 标准CA信任,信任我们的自定义策略; + final SSLContext sslcontext = SSLContexts.custom() + .loadTrustMaterial(new TrustStrategy() { + + @Override + public boolean isTrusted( + final X509Certificate[] chain, + final String authType) throws CertificateException { + final X509Certificate cert = chain[0]; + return "CN=httpbin.org".equalsIgnoreCase(cert.getSubjectDN().getName()); + } + + }) + .build(); + // 只允许 TLSv1.2 协议 + final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create() + .setSslContext(sslcontext) + .setTlsVersions(TLS.V_1_2) + .build(); + final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create() + .setSSLSocketFactory(sslSocketFactory) + .build(); + + try (CloseableHttpClient httpclient = HttpClients.custom() + .setConnectionManager(cm) + .build()) { + + final HttpGet httpget = new HttpGet("https://httpbin.org/get"); + + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + + final HttpClientContext clientContext = HttpClientContext.create(); + try (CloseableHttpResponse response = httpclient.execute(httpget, clientContext)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + + final SSLSession sslSession = clientContext.getSSLSession(); + if (sslSession != null) { + System.out.println("SSL 协议 " + sslSession.getProtocol()); + System.out.println("SSL cipher suite " + sslSession.getCipherSuite()); + } + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ExecuteProxy.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ExecuteProxy.java new file mode 100644 index 0000000..b5312b1 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ExecuteProxy.java @@ -0,0 +1,46 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * 如何通过代理发送请求。 + * +* @author https://www.wdbyte.com + * @date 2022/06/26 + */ +public class HttpClient5ExecuteProxy { + + public static void main(String[] args) { + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + final HttpHost target = new HttpHost("https", "httpbin.org", 443); + final HttpHost proxy = new HttpHost("http", "127.0.0.1", 8080); + + final RequestConfig config = RequestConfig.custom() + .setProxy(proxy) + .build(); + final HttpGet request = new HttpGet("/get"); + request.setConfig(config); + + System.out.println("执行请求 " + request.getMethod() + " " + request.getUri() + " via " + proxy); + + try (final CloseableHttpResponse response = httpclient.execute(target, request)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } catch (IOException | ParseException | URISyntaxException e) { + throw new RuntimeException(e); + } + } +} + diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5FormLogin.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5FormLogin.java new file mode 100644 index 0000000..fed0fda --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5FormLogin.java @@ -0,0 +1,58 @@ +package com.wdbyte.httpclient; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.entity.UrlEncodedFormEntity; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.NameValuePair; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.message.BasicNameValuePair; + +/** + * 演示基于表单的登录 + * + * @author https://www.wdbyte.com + */ +public class HttpClient5FormLogin { + + public static void main(final String[] args) throws Exception { + final BasicCookieStore cookieStore = new BasicCookieStore(); + try (final CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCookieStore(cookieStore) + .build()) { + + // 本应该使用 POST 请求发送表单参数,但是在 httpbin.org 中没有对应的接口用于测试,所以这里换成了 GET 请求 + // HttpPost httpPost = new HttpPost("http://httpbin.org/cookies/set/username/wdbyte.com"); + HttpGet httpPost = new HttpGet("http://httpbin.org/cookies/set/username/wdbyte.com"); + // POST 表单请求参数 + List nvps = new ArrayList<>(); + nvps.add(new BasicNameValuePair("username", "wdbyte.com")); + nvps.add(new BasicNameValuePair("password", "secret")); + httpPost.setEntity(new UrlEncodedFormEntity(nvps)); + + try (final CloseableHttpResponse response2 = httpclient.execute(httpPost)) { + final HttpEntity entity = response2.getEntity(); + + System.out.println("Login form get: " + response2.getCode() + " " + response2.getReasonPhrase()); + System.out.println("当前响应信息 "+EntityUtils.toString(entity));; + + System.out.println("Post 登录 Cookie:"); + final List cookies = cookieStore.getCookies(); + if (cookies.isEmpty()) { + System.out.println("None"); + } else { + for (int i = 0; i < cookies.size(); i++) { + System.out.println("- " + cookies.get(i)); + } + } + } + } + } +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithBasicAuth.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithBasicAuth.java new file mode 100644 index 0000000..9c6bb80 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithBasicAuth.java @@ -0,0 +1,51 @@ +//package com.wdbyte.httpclient.get; +// +//import org.apache.hc.client5.http.auth.AuthScope; +//import org.apache.hc.client5.http.auth.CredentialsProvider; +//import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +//import org.apache.hc.client5.http.classic.methods.HttpGet; +//import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +//import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +//import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +//import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +//import org.apache.hc.core5.http.HttpEntity; +//import org.apache.hc.core5.http.io.entity.EntityUtils; +// +///** +//* @author https://www.wdbyte.com +// * @date 2022/06/25 +// */ +//public class HttpClient5GetWithBasicAuth { +// +// public static void main(String[] args) { +// +// } +// +// public static String get(String url, String username, String password) { +// HttpGet request = new HttpGet(url); +// BasicCredentialsProvider provider = new BasicCredentialsProvider(); +// provider.setCredentials(AuthScope.); +// +// provider.setCredentials( +// AuthScope.ANY, +// new UsernamePasswordCredentials("user", "password") +// ); +// +// try (CloseableHttpClient httpClient = HttpClientBuilder.create() +// .setDefaultCredentialsProvider(provider) +// .build(); +// CloseableHttpResponse response = httpClient.execute(request)) { +// +// // 401 if wrong user/password +// System.out.println(response.getStatusLine().getStatusCode()); +// +// HttpEntity entity = response.getEntity(); +// if (entity != null) { +// // return it as a String +// String result = EntityUtils.toString(entity); +// System.out.println(result); +// } +// +// } +// } +//} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithTimeout.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithTimeout.java new file mode 100644 index 0000000..c199ca7 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5GetWithTimeout.java @@ -0,0 +1,54 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; + +/** +* @author https://www.wdbyte.com + */ +public class HttpClient5GetWithTimeout { + + public static void main(String[] args) { + String result = get("http://httpbin.org/get"); + System.out.println(result); + } + + public static String get(String url) { + String resultContent = null; + // 设置超时时间 + RequestConfig config = RequestConfig.custom() + .setConnectTimeout(Timeout.ofMilliseconds(5000L)) + .setConnectionRequestTimeout(Timeout.ofMilliseconds(5000L)) + .setResponseTimeout(Timeout.ofMilliseconds(5000L)) + .build(); + // 请求级别的超时 + HttpGet httpGet = new HttpGet(url); + //httpGet.setConfig(config); + //try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + // 客户端级别的超时 + try (CloseableHttpClient httpclient = HttpClients.custom().setDefaultRequestConfig(config).build()) { + try (CloseableHttpResponse response = httpclient.execute(httpGet)) { + // 获取状态码 + System.out.println(response.getVersion()); // HTTP/1.1 + System.out.println(response.getCode()); // 200 + System.out.println(response.getReasonPhrase()); // OK + HttpEntity entity = response.getEntity(); + // 获取响应信息 + resultContent = EntityUtils.toString(entity); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return resultContent; + } + +} diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Interceptors.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Interceptors.java new file mode 100644 index 0000000..6a5d6f3 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5Interceptors.java @@ -0,0 +1,81 @@ +package com.wdbyte.httpclient; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicLong; + +import org.apache.hc.client5.http.classic.ExecChain; +import org.apache.hc.client5.http.classic.ExecChain.Scope; +import org.apache.hc.client5.http.classic.ExecChainHandler; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.ChainElement; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.message.BasicClassicHttpResponse; +import org.apache.hc.core5.http.protocol.HttpContext; + +/** + * 展示如何在请求和响应时进行拦截进行自定义处理。 + */ +public class HttpClient5Interceptors { + + public static void main(final String[] args) throws Exception { + try (final CloseableHttpClient httpclient = HttpClients.custom() + // 添加一个请求 id 到请求 header + .addRequestInterceptorFirst(new HttpRequestInterceptor() { + private final AtomicLong count = new AtomicLong(0); + @Override + public void process( + final HttpRequest request, + final EntityDetails entity, + final HttpContext context) throws HttpException, IOException { + request.setHeader("request-id", Long.toString(count.incrementAndGet())); + } + }) + .addExecInterceptorAfter(ChainElement.PROTOCOL.name(), "custom", new ExecChainHandler() { + // 请求 id 为 2 的,模拟 404 响应,并自定义响应的内容。 + @Override + public ClassicHttpResponse execute( + final ClassicHttpRequest request, + final Scope scope, + final ExecChain chain) throws IOException, HttpException { + + final Header idHeader = request.getFirstHeader("request-id"); + if (idHeader != null && "2".equalsIgnoreCase(idHeader.getValue())) { + final ClassicHttpResponse response = new BasicClassicHttpResponse(HttpStatus.SC_NOT_FOUND, + "Oppsie"); + response.setEntity(new StringEntity("bad luck", ContentType.TEXT_PLAIN)); + return response; + } else { + return chain.proceed(request, scope); + } + } + }) + .build()) { + + for (int i = 0; i < 3; i++) { + final HttpGet httpget = new HttpGet("http://httpbin.org/get"); + + try (final CloseableHttpResponse response = httpclient.execute(httpget)) { + System.out.println("----------------------------------------"); + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveBasicAuthentication.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveBasicAuthentication.java new file mode 100644 index 0000000..e0bb8a7 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveBasicAuthentication.java @@ -0,0 +1,59 @@ +package com.wdbyte.httpclient; + +import org.apache.hc.client5.http.auth.AuthCache; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.auth.BasicAuthCache; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.auth.BasicScheme; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * 抢先式身份认证 + * 注意安全性问题 + */ +public class HttpClient5PreemptiveBasicAuthentication { + + public static void main(final String[] args) throws Exception { + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + + // 生成一个认证信息 + final BasicScheme basicAuth = new BasicScheme(); + basicAuth.initPreemptive(new UsernamePasswordCredentials("admin", "123456".toCharArray())); + // 认证信息的生效域名信息 + final HttpHost target = new HttpHost("http", "httpbin.org", 80); + // 添加认证信息到 HttpClient 请求上下文中 + final HttpClientContext localContext = HttpClientContext.create(); + localContext.resetAuthExchange(target, basicAuth); + + //AuthScope authScope = new AuthScope("httpbin.org", 80); + //BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + //credsProvider.setCredentials(authScope, + // new UsernamePasswordCredentials("admin", "1234556".toCharArray())); + + AuthCache authCache = new BasicAuthCache(); + authCache.put(target, new BasicScheme()); + //localContext.setCredentialsProvider(credsProvider); + localContext.setAuthCache(authCache); + + final HttpGet httpget = new HttpGet("http://httpbin.org/basic-auth/admin/123456"); + + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + for (int i = 0; i < 3; i++) { + try (final CloseableHttpResponse response = httpclient.execute(httpget, localContext)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveDigestAuthentication.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveDigestAuthentication.java new file mode 100644 index 0000000..5e3772b --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5PreemptiveDigestAuthentication.java @@ -0,0 +1,104 @@ +package com.wdbyte.httpclient; + +import org.apache.hc.client5.http.auth.AuthExchange; +import org.apache.hc.client5.http.auth.AuthScheme; +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.auth.DigestScheme; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * GET /digest-auth/auth/user/1234567 HTTP/1.1 + * Accept-Encoding: gzip, x-gzip, deflate + * Host: httpbin.org + * Connection: keep-alive + * User-Agent: Apache-HttpClient/5.1.3 (Java/17) + * + * HTTP/1.1 401 UNAUTHORIZED + * Date: Mon, 27 Jun 2022 01:14:33 GMT + * Content-Type: text/html; charset=utf-8 + * Content-Length: 0 + * Connection: keep-alive + * Server: gunicorn/19.9.0 + * WWW-Authenticate: Digest realm="me@kennethreitz.com", nonce="5555b4af2e521ae090278b68bdc0a514", qop="auth", + * opaque="ba9c52e5ba8efb290bbf44404c9df42c", algorithm=MD5, stale=FALSE + * Set-Cookie: stale_after=never; Path=/ + * Set-Cookie: fake=fake_value; Path=/ + * Access-Control-Allow-Origin: * + * Access-Control-Allow-Credentials: true + * + * GET /digest-auth/auth/user/1234567 HTTP/1.1 + * Host: httpbin.org + * Connection: keep-alive + * User-Agent: Apache-HttpClient/5.1.3 (Java/17) + * Cookie: fake=fake_value; stale_after=never + * Authorization: Digest username="user", realm="me@kennethreitz.com", nonce="5555b4af2e521ae090278b68bdc0a514", + * uri="/digest-auth/auth/user/1234567", response="ceef33b582fcbe9b15157d29a75f4781", qop=auth, nc=00000001, + * cnonce="085c5561765f5b9a", algorithm=MD5, opaque="ba9c52e5ba8efb290bbf44404c9df42c" + * + * HTTP/1.1 200 OK + * Date: Mon, 27 Jun 2022 01:14:33 GMT + * Content-Type: application/json + * Content-Length: 47 + * Connection: keep-alive + * Server: gunicorn/19.9.0 + * Set-Cookie: fake=fake_value; Path=/ + * Set-Cookie: stale_after=never; Path=/ + * Access-Control-Allow-Origin: * + * Access-Control-Allow-Credentials: true + * + * { + * "authenticated": true, + * "user": "user" + * } + * + * HttpClient如何验证多个请求的示例 + * 使用相同的摘要方案。在初始请求/响应交换之后 + * 共享相同执行上下文的所有后续请求都可以重用 + * 要向服务器进行身份验证的最后一个摘要nonce值。 + */ +public class HttpClient5PreemptiveDigestAuthentication { + + public static void main(final String[] args) throws Exception { + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + + final HttpHost target = new HttpHost("http", "httpbin.org", 80); + + final HttpClientContext localContext = HttpClientContext.create(); + final BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider(); + credentialsProvider.setCredentials( + new AuthScope(target), + new UsernamePasswordCredentials("admin", "123456".toCharArray())); + localContext.setCredentialsProvider(credentialsProvider); + + final HttpGet httpget = new HttpGet("http://httpbin.org/digest-auth/auth/admin/123456"); + + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + for (int i = 0; i < 2; i++) { + try (final CloseableHttpResponse response = httpclient.execute(target, httpget, localContext)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + EntityUtils.consume(response.getEntity()); + + final AuthExchange authExchange = localContext.getAuthExchange(target); + if (authExchange != null) { + final AuthScheme authScheme = authExchange.getAuthScheme(); + if (authScheme instanceof DigestScheme) { + final DigestScheme digestScheme = (DigestScheme) authScheme; + System.out.println("Nonce: " + digestScheme.getNonce() + + "; count: " + digestScheme.getNounceCount()); + } + } + } + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyAuthentication.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyAuthentication.java new file mode 100644 index 0000000..97c115e --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyAuthentication.java @@ -0,0 +1,48 @@ +package com.wdbyte.httpclient; + +import org.apache.hc.client5.http.auth.AuthScope; +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.RequestConfig; +import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * A simple example that uses HttpClient to execute an HTTP request + * over a secure connection tunneled through an authenticating proxy. + */ +public class HttpClient5ProxyAuthentication { + + public static void main(final String[] args) throws Exception { + final BasicCredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( // 代理和认证信息 + new AuthScope("localhost", 8888), + new UsernamePasswordCredentials("squid", "squid".toCharArray())); + credsProvider.setCredentials( // 目标和认证信息 + new AuthScope("httpbin.org", 80), + new UsernamePasswordCredentials("user", "passwd".toCharArray())); + try (final CloseableHttpClient httpclient = HttpClients.custom() + .setDefaultCredentialsProvider(credsProvider).build()) { + final HttpHost target = new HttpHost("http", "httpbin.org", 80); + final HttpHost proxy = new HttpHost("localhost", 8888); + + final RequestConfig config = RequestConfig.custom() + .setProxy(proxy) + .build(); + final HttpGet httpget = new HttpGet("/basic-auth/user/passwd"); + httpget.setConfig(config); + + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri() + " via " + proxy); + + try (final CloseableHttpResponse response = httpclient.execute(target, httpget)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + System.out.println(EntityUtils.toString(response.getEntity())); + } + } + } +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyTunnelDemo.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyTunnelDemo.java new file mode 100644 index 0000000..7a6b491 --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5ProxyTunnelDemo.java @@ -0,0 +1,42 @@ +package com.wdbyte.httpclient; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; +import org.apache.hc.client5.http.impl.classic.ProxyClient; +import org.apache.hc.core5.http.HttpHost; + +/** + * Example code for using {@link ProxyClient} in order to establish a tunnel through an HTTP proxy. + */ +public class HttpClient5ProxyTunnelDemo { + + public final static void main(final String[] args) throws Exception { + + final ProxyClient proxyClient = new ProxyClient(); + final HttpHost target = new HttpHost("www.yahoo.com", 80); + final HttpHost proxy = new HttpHost("127.0.0.1", 9090); + final UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("user", "pwd".toCharArray()); + try (final Socket socket = proxyClient.tunnel(proxy, target, credentials)) { + final Writer out = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.ISO_8859_1); + out.write("GET / HTTP/1.1\r\n"); + out.write("Host: " + target.toHostString() + "\r\n"); + out.write("Agent: whatever\r\n"); + out.write("Connection: close\r\n"); + out.write("\r\n"); + out.flush(); + final BufferedReader in = new BufferedReader( + new InputStreamReader(socket.getInputStream(), StandardCharsets.ISO_8859_1)); + String line = null; + while ((line = in.readLine()) != null) { + System.out.println(line); + } + } + } + +} \ No newline at end of file diff --git a/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5WithCookie.java b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5WithCookie.java new file mode 100644 index 0000000..af0eace --- /dev/null +++ b/tool-java-apache-httpclient/src/main/java/com/wdbyte/httpclient/HttpClient5WithCookie.java @@ -0,0 +1,53 @@ +package com.wdbyte.httpclient; + +import java.util.List; + +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.cookie.BasicCookieStore; +import org.apache.hc.client5.http.cookie.Cookie; +import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.cookie.BasicClientCookie; +import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.http.io.entity.EntityUtils; + +/** + * 这个例子演示了使用本地HTTP上下文填充, 自定义属性 + */ +public class HttpClient5WithCookie { + + public static void main(final String[] args) throws Exception { + try (final CloseableHttpClient httpclient = HttpClients.createDefault()) { + // 创建一个本地的 Cookie 存储 + final CookieStore cookieStore = new BasicCookieStore(); + //BasicClientCookie clientCookie = new BasicClientCookie("name", "www.wdbyte.com"); + //clientCookie.setDomain("http://httpbin.org/cookies"); + // 过期时间 + //clientCookie.setExpiryDate(new Date()); + // 添加到本地 Cookie + //cookieStore.addCookie(clientCookie); + + // 创建本地 HTTP 请求上下文 HttpClientContext + final HttpClientContext localContext = HttpClientContext.create(); + // 绑定 cookieStore 到 localContext + localContext.setCookieStore(cookieStore); + + final HttpGet httpget = new HttpGet("http://httpbin.org/cookies/set/cookieName/www.wdbyte.com"); + System.out.println("执行请求 " + httpget.getMethod() + " " + httpget.getUri()); + + // 获取 Coolie 信息 + try (final CloseableHttpResponse response = httpclient.execute(httpget, localContext)) { + System.out.println("----------------------------------------"); + System.out.println(response.getCode() + " " + response.getReasonPhrase()); + final List cookies = cookieStore.getCookies(); + for (int i = 0; i < cookies.size(); i++) { + System.out.println("Local cookie: " + cookies.get(i)); + } + EntityUtils.consume(response.getEntity()); + } + } + } + +} \ No newline at end of file From 68041649815e956a70b4a6755f5201f2822c64ca Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 2 Nov 2022 00:03:42 +0800 Subject: [PATCH 033/105] =?UTF-8?q?feat:[=E4=BD=BF=E7=94=A8=20StringUtils.?= =?UTF-8?q?split=20=E7=9A=84=E5=9D=91](https://www.wdbyte.com/java/stringu?= =?UTF-8?q?tils=5Fsplit.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + tool-java-apache-common/pom.xml | 20 ++++++ .../com/wdbyte/string/StringUtilsTest.java | 70 +++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 tool-java-apache-common/pom.xml create mode 100644 tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java diff --git a/pom.xml b/pom.xml index 0fe1bcd..1f62483 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ tool-java-apache-httpclient tool-java-object-pool tool-java-jackson + tool-java-apache-common parent-modules Parent for all java modules diff --git a/tool-java-apache-common/pom.xml b/tool-java-apache-common/pom.xml new file mode 100644 index 0000000..895fe6b --- /dev/null +++ b/tool-java-apache-common/pom.xml @@ -0,0 +1,20 @@ + + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + + tool-java-apache-common + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java b/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java new file mode 100644 index 0000000..fdaedda --- /dev/null +++ b/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java @@ -0,0 +1,70 @@ +package com.wdbyte.string; + +import java.util.Arrays; + +import org.apache.commons.lang3.StringUtils; + +/** + * apache common lang StringUtils test + * @author niulang + * @date 2022/11/01 + */ +public class StringUtilsTest { + + public static void testA() { + String str = "aabbccdd"; + String[] resultArray = StringUtils.split(str, "bc"); + for (String s : resultArray) { + System.out.println(s); + } + } + + public static void testB() { + String str = "abc"; + String[] resultArray = StringUtils.split(str, "ac"); + for (String s : resultArray) { + System.out.println(s); + } + } + + public static void testC() { + String str = "abcd"; + String[] resultArray = StringUtils.split(str, "ac"); + for (String s : resultArray) { + System.out.println(s); + } + } + + public static void testD() { + String str = "aabbccdd"; + String[] resultArray = StringUtils.splitByWholeSeparator(str, "bc"); + for (String s : resultArray) { + System.out.println(s); + } + } + + public static void testE() { + //String str = "aabbccdd"; + //Iterable iterable = Splitter.on("bc") + // .omitEmptyStrings() // 忽略空值 + // .trimResults() // 过滤结果中的空白 + // .split(str); + //iterable.forEach(System.out::println); + } + + public static void testF() { + String str = "aabbccdd"; + String[] res = str.split("bc"); + for (String re : res) { + System.out.println(re); + } + + } + + public static void testG() { + String str = ",a,,b,"; + String[] splitArr = str.split(","); + Arrays.stream(splitArr).forEach(System.out::println); + } + +} From e26df19a8d0a1f8e4720a988b10995d26db15c94 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 2 Nov 2022 00:04:02 +0800 Subject: [PATCH 034/105] =?UTF-8?q?feat:[=E4=BD=BF=E7=94=A8=20StringUtils.?= =?UTF-8?q?split=20=E7=9A=84=E5=9D=91](https://www.wdbyte.com/java/stringu?= =?UTF-8?q?tils=5Fsplit.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 26d8a9e..31fe748 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ ## ⏳ Java 开发 +- [使用 StringUtils.split 的坑](https://www.wdbyte.com/java/stringutils_split.html) - [必应壁纸,我的第一个 400 Star 开源项目](https://www.wdbyte.com/bing-wallpaper-400.html) - [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html) - [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) From f903692fde2ec5c4e94689eb56ff9cf75a1a20f8 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 17 Nov 2022 23:23:47 +0800 Subject: [PATCH 035/105] =?UTF-8?q?feat:[JUnit=205=20=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E6=95=99=E7=A8=8B](https://www.wdbyte.com/java/junit5.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- junit5-jupiter-starter/pom.xml | 24 +++------ .../java/com/wdbyte/test/junit5/Person.java | 12 +++++ .../wdbyte/test/junit5/JUnitBeforeAll.java | 54 +++++++++++++++++++ .../wdbyte/test/junit5/JUnitJDKVersion.java | 38 +++++++++++++ .../com/wdbyte/test/junit5/JUnitOrder.java | 35 ++++++++++++ .../com/wdbyte/test/junit5/JUnitOther.java | 37 +++++++++++++ .../com/wdbyte/test/junit5/JUnitParam.java | 21 ++++++++ .../wdbyte/test/junit5/JUnitTestIsDog.java | 29 ++++++++++ .../com/wdbyte/test/junit5/JunitAssert.java | 31 +++++++++++ .../com/wdbyte/test/junit5/PersonTest.java | 20 +++++++ 10 files changed, 284 insertions(+), 17 deletions(-) create mode 100644 junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java create mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java diff --git a/junit5-jupiter-starter/pom.xml b/junit5-jupiter-starter/pom.xml index b41ccff..d100632 100644 --- a/junit5-jupiter-starter/pom.xml +++ b/junit5-jupiter-starter/pom.xml @@ -13,41 +13,31 @@ junit5-jupiter-starter - 17 - 17 + 1.8 + 1.8 UTF-8 ${maven.compiler.source} - - - - org.junit - junit-bom - 5.8.2 - pom - import - - - - org.junit.jupiter junit-jupiter + 5.9.1 test - maven-compiler-plugin - 3.8.1 + maven-surefire-plugin + 2.22.2 - maven-surefire-plugin + maven-failsafe-plugin 2.22.2 + \ No newline at end of file diff --git a/junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java b/junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java new file mode 100644 index 0000000..514c0af --- /dev/null +++ b/junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java @@ -0,0 +1,12 @@ +package com.wdbyte.test.junit5; + +/** + * @author niulang + * @date 2022/11/17 + */ +public class Person { + + public int getLuckyNumber() { + return 7; + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java new file mode 100644 index 0000000..732fb50 --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java @@ -0,0 +1,54 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2022/11/15 + */ +class JUnitBeforeAll { + + @BeforeAll + public static void init() { + System.out.println("初始化,准备测试信息"); + } + + @BeforeEach + public void start() { + System.out.println("开始测试..."); + } + + @DisplayName("是否是狗") + @Disabled("由于xx原因,关闭 testIsDog 测试") + @Test + public void testIsDog() { + String name = "dog"; + Assertions.assertEquals(name, "dog"); + System.out.println("is dog"); + } + + @DisplayName("是否是猫") + @Test + public void testIsCat() { + String name = "cat"; + Assertions.assertEquals(name, "cat"); + System.out.println("is cat"); + } + + @AfterEach + public void end() { + System.out.println("测试完毕..."); + } + + @AfterAll + public static void close() { + System.out.println("结束,准备退出测试"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java new file mode 100644 index 0000000..62230fc --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java @@ -0,0 +1,38 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.condition.EnabledOnJre; + +import static org.junit.jupiter.api.condition.JRE.JAVA_19; + +/** + * @author niulang + * @date 2022/11/17 + */ +@TestMethodOrder(OrderAnnotation.class) +public class JUnitJDKVersion { + + @Test + //@EnabledOnJre(JAVA_19) + @DisplayName("测试是否是狗") + @Order(2) + public void testIsDog() { + String name = "dog"; + Assertions.assertEquals(name, "dog"); + System.out.println("is dog"); + } + + @DisplayName("是否是猫") + @Test + @Order(10) + public void testIsCat() { + String name = "cat"; + Assertions.assertEquals(name, "cat"); + System.out.println("is cat"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java new file mode 100644 index 0000000..eef289a --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java @@ -0,0 +1,35 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author niulang + * @date 2022/11/17 + */ +@TestMethodOrder(OrderAnnotation.class) +public class JUnitOrder { + + @Test + //@EnabledOnJre(JAVA_19) + @DisplayName("测试是否是狗") + @Order(2) + public void testIsDog() { + String name = "dog"; + Assertions.assertEquals(name, "dog"); + System.out.println("is dog"); + } + + @DisplayName("是否是猫") + @Test + @Order(1) + public void testIsCat() { + String name = "cat"; + Assertions.assertEquals(name, "cat"); + System.out.println("is cat"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java new file mode 100644 index 0000000..aebe289 --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java @@ -0,0 +1,37 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +/** + * @author niulang + * @date 2022/11/17 + */ +@TestMethodOrder(OrderAnnotation.class) +public class JUnitOther { + + //@Test + //@EnabledOnJre(JAVA_19) + @DisplayName("测试是否是狗") + @Order(2) + @RepeatedTest(10) + public void testIsDog() { + String name = "dog"; + Assertions.assertEquals(name, "dog"); + System.out.println("is dog"); + } + + @DisplayName("是否是猫") + @Test + @Order(1) + public void testIsCat() { + String name = "cat"; + Assertions.assertEquals(name, "cat"); + System.out.println("is cat"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java new file mode 100644 index 0000000..fe29bbd --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java @@ -0,0 +1,21 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * @author niulang + * @date 2022/11/17 + */ +public class JUnitParam { + + //@Test + @DisplayName("是否是狗") + @ValueSource(strings = {"dog", "cat"}) + @ParameterizedTest(name = "开始测试入参 {0} ") + public void testIsDog(String name) { + Assertions.assertEquals(name, "dog"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java new file mode 100644 index 0000000..5d0cb07 --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java @@ -0,0 +1,29 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2022/11/15 + */ +class JUnitTestIsDog { + + @BeforeAll + public static void init() { + System.out.println("准备测试 Dog 信息"); + } + + @Test + public void testIsDog() { + String name = "cat"; + Assertions.assertEquals(name, "dog"); + } + + @Test + public void testIsDog2() { + String name = "dog"; + Assertions.assertEquals(name, "dog"); + } +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java new file mode 100644 index 0000000..0063739 --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java @@ -0,0 +1,31 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2022/11/17 + */ +public class JunitAssert { + + @DisplayName("是否是狗") + @Test + public void testIsDog() { + String name = "dog"; + Assertions.assertNotNull(name); + Assertions.assertEquals(name, "dog"); + Assertions.assertNotEquals(name, "cat"); + Assertions.assertTrue("dog".equals(name)); + Assertions.assertFalse("cat".equals(name)); + } + + @DisplayName("是否是猫") + @Test + public void testIsCat() { + String name = "cat"; + Assertions.assertNull(name, "name is not null"); + } + +} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java new file mode 100644 index 0000000..0d883ed --- /dev/null +++ b/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java @@ -0,0 +1,20 @@ +package com.wdbyte.test.junit5; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2022/11/17 + */ +@DisplayName("测试 Presion") +class PersonTest { + + @DisplayName("测试幸运数字") + @Test + void getLuckyNumber() { + Person person = new Person(); + Assertions.assertEquals(8, person.getLuckyNumber()); + } +} \ No newline at end of file From c1e2e590e35b82506fa770646c20cfa8f2c6b963 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 17 Nov 2022 23:25:07 +0800 Subject: [PATCH 036/105] =?UTF-8?q?feat:[JUnit=205=20=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=99=E7=A8=8B](https://www.wdbyte.com/ja?= =?UTF-8?q?va/junit5.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + .../java/com/wdbyte/junit5/Calculator.java | 32 ------------------- .../com/wdbyte/junit5/CalculatorTest.java | 30 ----------------- tool-java-jackson/README.md | 2 +- 4 files changed, 2 insertions(+), 63 deletions(-) delete mode 100644 junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java delete mode 100644 junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java diff --git a/README.md b/README.md index 31fe748..4bff109 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ ## ⏳ Java 开发 +- [JUnit5 单元测试教程](https://www.wdbyte.com/java/junit5.html) - [使用 StringUtils.split 的坑](https://www.wdbyte.com/java/stringutils_split.html) - [必应壁纸,我的第一个 400 Star 开源项目](https://www.wdbyte.com/bing-wallpaper-400.html) - [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html) diff --git a/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java b/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java deleted file mode 100644 index 56a1037..0000000 --- a/junit5-jupiter-starter/src/main/java/com/wdbyte/junit5/Calculator.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.wdbyte.junit5; - -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.nio.charset.Charset; - -/** - * @author niulang - * @date 2022/03/16 - */ -public class Calculator { - - public int add(int x, int y) { - return x + y; - } - public static void main(String[] args) throws UnknownHostException { - InetAddress byName = InetAddress.getByName("www.baidu.com"); - System.out.println(byName.getHostAddress()); - //Java使用的默认字符集 - System.out.println("java 默认字符集:"); - System.out.println(Charset.defaultCharset()+"\n"); - //汉字“测”的字节编码 - String str = "测试一下"; - //这里可以手动设置编码字符集,默认使用utf-8编码 - byte[] bytes = str.getBytes(); - System.out.println("汉字\"测\"的编码:"); - for(byte bt: bytes){ - System.out.println(bt); - } - - } -} diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java b/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java deleted file mode 100644 index 2005ed6..0000000 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/junit5/CalculatorTest.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.wdbyte.junit5; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.RepeatedTest; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * @author niulang - * @date 2022/03/16 - */ -@DisplayName("计算器") -class CalculatorTest { - - private final Calculator calculator = new Calculator(); - - //@Tag("fast") - @Test - //@RepeatedTest(2) - @DisplayName("相加") - //@ParameterizedTest() - //@ValueSource(ints = { -1, -4 }) - void add() { - assertEquals(3, calculator.add(1, 1)); - } -} \ No newline at end of file diff --git a/tool-java-jackson/README.md b/tool-java-jackson/README.md index 0b9e3f0..bd31197 100644 --- a/tool-java-jackson/README.md +++ b/tool-java-jackson/README.md @@ -1,4 +1,4 @@ ## tool-java-jackson ### 相关文章 -- [Jackson 解析 JSON 教程](https://www.wdbyte.com/tool/jackson.html) +- [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) From 36191924707171af075faf13b49d373611c143d5 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 18 Nov 2022 10:01:02 +0800 Subject: [PATCH 037/105] =?UTF-8?q?feat:[JUnit=205=20=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=95=99=E7=A8=8B](https://www.wdbyte.com/ja?= =?UTF-8?q?va/junit5.html)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- junit5-jupiter-starter/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 junit5-jupiter-starter/README.md diff --git a/junit5-jupiter-starter/README.md b/junit5-jupiter-starter/README.md new file mode 100644 index 0000000..6773392 --- /dev/null +++ b/junit5-jupiter-starter/README.md @@ -0,0 +1,3 @@ +## junit5-jupiter-starter + +- [JUnit5 单元测试教程](https://www.wdbyte.com/java/junit5.html) \ No newline at end of file From 17af0f453ccbdbe15895a956de61089ce3227c60 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 21 Feb 2023 21:46:21 +0800 Subject: [PATCH 038/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + tool-java-hotcode/pom.xml | 16 ++ .../main/java/com/wdbyte/hotcode/HotCode.java | 167 ++++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 tool-java-hotcode/pom.xml create mode 100644 tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java diff --git a/pom.xml b/pom.xml index 1f62483..3110d06 100644 --- a/pom.xml +++ b/pom.xml @@ -16,6 +16,7 @@ tool-java-object-pool tool-java-jackson tool-java-apache-common + tool-java-hotcode parent-modules Parent for all java modules diff --git a/tool-java-hotcode/pom.xml b/tool-java-hotcode/pom.xml new file mode 100644 index 0000000..8ff3731 --- /dev/null +++ b/tool-java-hotcode/pom.xml @@ -0,0 +1,16 @@ + + + parent-modules + com.wdbyte + 1.0.0-SNAPSHOT + + 4.0.0 + tool-java-hotcode + + + + 17 + 17 + + diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java new file mode 100644 index 0000000..46413c7 --- /dev/null +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -0,0 +1,167 @@ +package com.wdbyte.hotcode; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author niulang + * @date 2023/02/20 + */ +public class HotCode { + + private static HashSet hashSet = new HashSet(); + /** + * 线程池,大小1 + */ + private static ExecutorService executorService = Executors.newFixedThreadPool(1); + + public static void main(String[] args) { + // 模拟 CPU 过高 + //cpuHigh(); + // 模拟线程阻塞,线程池容量为1,塞入两个线程,会有一个一直等待 + thread(); + // 模拟线程死锁 + deadThread(); + // 不断的向 hashSet 集合增加数据,内存缓慢增长 + addHashSetThread(); + // 生成大长度数组 + allocate(); + } + + private static Object array; + + /** + * 生成大长度数组 + */ + private static void allocate() { + new Thread(() -> { + int index = 1; + while (true) { + Thread.currentThread().setName("memory_allocate_thread"); + array = new int[1 * index * 1000]; + array = new Integer[1 * index * 1000]; + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + index++; + + } + }).start(); + + new Thread(()->{ + List list = new ArrayList<>(); + for (int i = 0; i < 1000000; i++) { + try { + Thread.sleep(1); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + list.add("string" + i); + } + }).start(); + } + + /** + * 不断的向 hashSet 集合添加数据,每秒100个字符串 + */ + public static void addHashSetThread() { + // 初始化常量 + new Thread(() -> { + int count = 0; + while (true) { + Thread.currentThread().setName("add_hash_set_thread"); + try { + hashSet.add("count" + count); + Thread.sleep(10); + count++; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + } + + /** + * 极度消耗CPU的线程 + * 死循环 + */ + private static void cpuHigh() { + Thread thread = new Thread(() -> { + double pi = 0; + for (int i = 0; i < Integer.MAX_VALUE; i++) { + pi += Math.pow(-1, i) / (2 * i + 1); + } + System.out.println("Pi: " + pi * 4); + }); + thread.start(); + } + + /** + * 模拟线程阻塞 + * 线程池容量为1,但是向线程池中塞入两个线程 + */ + private static void thread() { + Thread thread = new Thread(() -> { + while (true) { + System.out.println("executorService thread start"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + // 添加到线程 + executorService.submit(thread); + executorService.submit(thread); + } + + /** + * 死锁线程 + * 线程 dead_thread_A 与 线程 dead_thread_B 互相锁死 + */ + private static void deadThread() { + /** 创建资源 */ + Object resourceA = new Object(); + Object resourceB = new Object(); + // 创建线程 + Thread threadA = new Thread(() -> { + Thread.currentThread().setName("dead_thread_A"); + synchronized (resourceA) { + System.out.println(Thread.currentThread() + " get ResourceA"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(Thread.currentThread() + "waiting get resourceB"); + synchronized (resourceB) { + System.out.println(Thread.currentThread() + " get resourceB"); + } + } + }); + + Thread threadB = new Thread(() -> { + Thread.currentThread().setName("dead_thread_A"); + synchronized (resourceB) { + System.out.println(Thread.currentThread() + " get ResourceB"); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(Thread.currentThread() + "waiting get resourceA"); + synchronized (resourceA) { + System.out.println(Thread.currentThread() + " get resourceA"); + } + } + }); + threadA.start(); + threadB.start(); + } +} From 6ccf78a43945ab72f7ce13e79af3f803bcc01a8e Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 21 Feb 2023 22:11:16 +0800 Subject: [PATCH 039/105] feat: opt hotcode --- .../src/main/java/com/wdbyte/hotcode/HotCode.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index 46413c7..8c867fa 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -20,7 +20,7 @@ public class HotCode { public static void main(String[] args) { // 模拟 CPU 过高 - //cpuHigh(); + cpuHigh(); // 模拟线程阻塞,线程池容量为1,塞入两个线程,会有一个一直等待 thread(); // 模拟线程死锁 @@ -92,11 +92,14 @@ public static void addHashSetThread() { */ private static void cpuHigh() { Thread thread = new Thread(() -> { - double pi = 0; - for (int i = 0; i < Integer.MAX_VALUE; i++) { - pi += Math.pow(-1, i) / (2 * i + 1); + Thread.currentThread().setName("cpu_high_thread"); + while (true){ + double pi = 0; + for (int i = 0; i < Integer.MAX_VALUE; i++) { + pi += Math.pow(-1, i) / (2 * i + 1); + } + System.out.println("Pi: " + pi * 4); } - System.out.println("Pi: " + pi * 4); }); thread.start(); } From 45ef97b4fba79f10c50eecbfcc3d7ffbe3c01189 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sat, 25 Feb 2023 16:24:01 +0800 Subject: [PATCH 040/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/wdbyte/hotcode/HotCode.java | 137 ++++++++++-------- 1 file changed, 74 insertions(+), 63 deletions(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index 8c867fa..db7eb11 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -21,16 +21,33 @@ public class HotCode { public static void main(String[] args) { // 模拟 CPU 过高 cpuHigh(); - // 模拟线程阻塞,线程池容量为1,塞入两个线程,会有一个一直等待 - thread(); + // 生成大长度数组 + allocate(); // 模拟线程死锁 deadThread(); // 不断的向 hashSet 集合增加数据,内存缓慢增长 addHashSetThread(); - // 生成大长度数组 - allocate(); + // 模拟线程阻塞,线程池容量为1,塞入两个线程,会有一个一直等待 + thread(); } + /** + * 消耗CPU的线程 + * 不断循环进行浮点运算 + */ + private static void cpuHigh() { + Thread thread = new Thread(() -> { + Thread.currentThread().setName("cpu_high_thread"); + while (true){ + double pi = 0; + for (int i = 0; i < Integer.MAX_VALUE; i++) { + pi += Math.pow(-1, i) / (2 * i + 1); + } + System.out.println("Pi: " + pi * 4); + } + }); + thread.start(); + } private static Object array; /** @@ -38,9 +55,9 @@ public static void main(String[] args) { */ private static void allocate() { new Thread(() -> { + Thread.currentThread().setName("memory_allocate_thread_1"); int index = 1; while (true) { - Thread.currentThread().setName("memory_allocate_thread"); array = new int[1 * index * 1000]; array = new Integer[1 * index * 1000]; try { @@ -54,6 +71,7 @@ private static void allocate() { }).start(); new Thread(()->{ + Thread.currentThread().setName("memory_allocate_thread_2"); List list = new ArrayList<>(); for (int i = 0; i < 1000000; i++) { try { @@ -66,64 +84,6 @@ private static void allocate() { }).start(); } - /** - * 不断的向 hashSet 集合添加数据,每秒100个字符串 - */ - public static void addHashSetThread() { - // 初始化常量 - new Thread(() -> { - int count = 0; - while (true) { - Thread.currentThread().setName("add_hash_set_thread"); - try { - hashSet.add("count" + count); - Thread.sleep(10); - count++; - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }).start(); - } - - /** - * 极度消耗CPU的线程 - * 死循环 - */ - private static void cpuHigh() { - Thread thread = new Thread(() -> { - Thread.currentThread().setName("cpu_high_thread"); - while (true){ - double pi = 0; - for (int i = 0; i < Integer.MAX_VALUE; i++) { - pi += Math.pow(-1, i) / (2 * i + 1); - } - System.out.println("Pi: " + pi * 4); - } - }); - thread.start(); - } - - /** - * 模拟线程阻塞 - * 线程池容量为1,但是向线程池中塞入两个线程 - */ - private static void thread() { - Thread thread = new Thread(() -> { - while (true) { - System.out.println("executorService thread start"); - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - }); - // 添加到线程 - executorService.submit(thread); - executorService.submit(thread); - } - /** * 死锁线程 * 线程 dead_thread_A 与 线程 dead_thread_B 互相锁死 @@ -167,4 +127,55 @@ private static void deadThread() { threadA.start(); threadB.start(); } + + + /** + * 不断的向 hashSet 集合添加数据,每秒100个字符串 + */ + public static void addHashSetThread() { + // 初始化常量 + new Thread(() -> { + Thread.currentThread().setName("add_hash_set_thread"); + int count = 0; + while (true) { + try { + hashSet.add("count" + count); + Thread.sleep(10); + count++; + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + } + + /** + * 模拟线程阻塞 + * 线程池容量为1,但是向线程池中塞入两个线程 + */ + private static void thread() { + Thread thread = new Thread(() -> { + System.out.println("executorService thread start"); + while (true) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + // 添加到线程 + executorService.submit(thread); + executorService.submit(thread); + executorService.submit(thread); + executorService.submit(thread); + } + + /** + * 运行缓慢的方法 + */ + public static void runSlowThread(){ + new Thread(() -> { + }).start(); + } } From 4147125fafeb5652fdfbc8f3f06cd20130f478ac Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sat, 25 Feb 2023 21:58:13 +0800 Subject: [PATCH 041/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/wdbyte/hotcode/HotCode.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index db7eb11..a7ffa27 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -1,5 +1,6 @@ package com.wdbyte.hotcode; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -58,15 +59,13 @@ private static void allocate() { Thread.currentThread().setName("memory_allocate_thread_1"); int index = 1; while (true) { - array = new int[1 * index * 1000]; - array = new Integer[1 * index * 1000]; + array = new BigDecimal[1 * index * 1000]; try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } index++; - } }).start(); @@ -140,7 +139,7 @@ public static void addHashSetThread() { while (true) { try { hashSet.add("count" + count); - Thread.sleep(10); + Thread.sleep(5); count++; } catch (InterruptedException e) { e.printStackTrace(); From 080568bffeaca4bc1faeebf498fac985260720bc Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sat, 25 Feb 2023 22:28:28 +0800 Subject: [PATCH 042/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/wdbyte/hotcode/HotCode.java | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index a7ffa27..db3d5d3 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -22,7 +22,7 @@ public class HotCode { public static void main(String[] args) { // 模拟 CPU 过高 cpuHigh(); - // 生成大长度数组 + // 不断新增 BigDecimal 信息到 list allocate(); // 模拟线程死锁 deadThread(); @@ -49,36 +49,21 @@ private static void cpuHigh() { }); thread.start(); } - private static Object array; /** - * 生成大长度数组 + * 不断新增 BigDecimal 信息到 list */ private static void allocate() { - new Thread(() -> { - Thread.currentThread().setName("memory_allocate_thread_1"); - int index = 1; - while (true) { - array = new BigDecimal[1 * index * 1000]; - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - throw new RuntimeException(e); - } - index++; - } - }).start(); - new Thread(()->{ - Thread.currentThread().setName("memory_allocate_thread_2"); - List list = new ArrayList<>(); - for (int i = 0; i < 1000000; i++) { + Thread.currentThread().setName("memory_allocate_thread"); + List list = new ArrayList<>(); + for (int i = 0; i < Integer.MAX_VALUE; i++) { try { Thread.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } - list.add("string" + i); + list.add(new BigDecimal(i)); } }).start(); } From 2ca1c771666de241e1d17067adab138135bef262 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Mon, 6 Mar 2023 20:11:03 +0800 Subject: [PATCH 043/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E7=83=AD?= =?UTF-8?q?=E7=82=B9=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/wdbyte/hotcode/HotCode.java | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index db3d5d3..0750444 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -8,6 +8,7 @@ import java.util.concurrent.Executors; /** + * * @author niulang * @date 2023/02/20 */ @@ -22,7 +23,7 @@ public class HotCode { public static void main(String[] args) { // 模拟 CPU 过高 cpuHigh(); - // 不断新增 BigDecimal 信息到 list + // 不断新增 BigDecimal 信息到 list,每秒10000个,内存迅速上升 allocate(); // 模拟线程死锁 deadThread(); @@ -30,6 +31,8 @@ public static void main(String[] args) { addHashSetThread(); // 模拟线程阻塞,线程池容量为1,塞入两个线程,会有一个一直等待 thread(); + // 运行缓慢的方法 + runSlowThread(); } /** @@ -51,7 +54,7 @@ private static void cpuHigh() { } /** - * 不断新增 BigDecimal 信息到 list + * 不断新增 BigDecimal 信息到 list,每秒10000个 */ private static void allocate() { new Thread(()->{ @@ -63,7 +66,9 @@ private static void allocate() { } catch (InterruptedException e) { throw new RuntimeException(e); } - list.add(new BigDecimal(i)); + for (int i1 = 0; i1 < 10; i1++) { + list.add(new BigDecimal(i)); + } } }).start(); } @@ -160,6 +165,33 @@ private static void thread() { */ public static void runSlowThread(){ new Thread(() -> { + Thread.currentThread().setName("slow_method"); + while (true){ + try { + slow(); + slow2(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } }).start(); } + + public static void slow() throws InterruptedException { + int count = 0; + for (int i = 0; i < 10000; i++) { + count++; + Thread.sleep(1); + } + System.out.println(count); + } + public static void slow2() throws InterruptedException { + int count = 0; + for (int i = 0; i < 1000; i++) { + count++; + Thread.sleep(1); + } + System.out.println(count); + } + } From a8263f9fa14e6801b875177e7148255cf3801c98 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 30 Mar 2023 23:00:42 +0800 Subject: [PATCH 044/105] feat: add java-base --- core-java-modules/core-java-base/.gitignore | 38 +++++++++ core-java-modules/core-java-base/pom.xml | 12 +++ .../src/main/java/com/wdbyte/JavaArray.java | 77 +++++++++++++++++++ .../src/main/java/com/wdbyte/JavaArray3.java | 25 ++++++ .../main/java/com/wdbyte/JavaDataType.java | 36 +++++++++ .../src/main/java/com/wdbyte/JavaString.java | 68 ++++++++++++++++ .../java/com/wdbyte/JavaStringBuilder.java | 38 +++++++++ core-java-modules/core-java-collect/pom.xml | 20 +++++ core-java-modules/pom.xml | 2 + tool-java-hotcode/.gitignore | 38 +++++++++ 10 files changed, 354 insertions(+) create mode 100644 core-java-modules/core-java-base/.gitignore create mode 100644 core-java-modules/core-java-base/pom.xml create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java create mode 100644 core-java-modules/core-java-collect/pom.xml create mode 100644 tool-java-hotcode/.gitignore diff --git a/core-java-modules/core-java-base/.gitignore b/core-java-modules/core-java-base/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/core-java-modules/core-java-base/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/core-java-modules/core-java-base/pom.xml b/core-java-modules/core-java-base/pom.xml new file mode 100644 index 0000000..da2f162 --- /dev/null +++ b/core-java-modules/core-java-base/pom.xml @@ -0,0 +1,12 @@ + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + core-java-base + Archetype - core-java-base + http://maven.apache.org + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java new file mode 100644 index 0000000..f82fd63 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java @@ -0,0 +1,77 @@ +package com.wdbyte; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author niulang + * @date 2023/03/23 + */ +public class JavaArray { + + public static void main(String[] args) { + + // 数组定义 + String[] aaa = new String[11]; + System.out.println(aaa.length); + // 数组访问 + int[] arr = {1, 2, 3}; + System.out.println(arr.toString()); // 数组地址 + arr[0] = 10; + System.out.println(arr[0]); + + for (int a : arr) { + System.out.println(a); + } + for (int i = 0; i < arr.length; i++) { + System.out.println(arr[i]); + } + + // copy + String[] strArr = {"hello", "world", "!"}; + String[] strArrByClone = strArr.clone(); + System.out.println(String.join(" ", strArrByClone)); + // 输出:hello world ! + String[] strAyyByCopy = new String[5]; + System.arraycopy(strArr,1,strAyyByCopy,3,2); + for (String s : strAyyByCopy) { + System.out.println(s); + } + String[] newArr = Arrays.copyOfRange(strArr, 0, 2); + System.out.println(Arrays.toString(newArr)); + // 输出:null + //null + //null + //world + //! + + Person[][] personArr = new Person[2][2]; + personArr[0][0] = new Person("www"); + personArr[1][1] = new Person("wdbyte.com"); + Person[][] personArrByClone = personArr.clone(); + personArrByClone[0][0] = new Person("https://www"); + System.out.println(personArr[0][0].name); + System.out.println(personArrByClone[0][0].name); + + int[] numArr = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10}; + // 对数组进行排序 + Arrays.sort(numArr); + // 将数组转换成字符串输出 + System.out.println(Arrays.toString(numArr)); + // 将数组转换成 List + String[] strArray = new String[3]; + // 将数组的所有元素都赋为指定的值 + Arrays.fill(strArray,"byte"); + System.out.println(Arrays.toString(strArray)); + List stringList = Arrays.asList("a","b","c"); + System.out.println(stringList); + } + static class Person{ + public String name; + public Person(String name) { + this.name = name; + } + } + +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java new file mode 100644 index 0000000..cf6c175 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java @@ -0,0 +1,25 @@ +package com.wdbyte; + +/** + * @author niulang + * @date 2023/03/25 + */ +public class JavaArray3 { + + public static void main(String[] args) { + int[][] myArray1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + int[][] myArray2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + int[][] myArray3 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + int[][][] array3d = {myArray1, myArray2, myArray3}; + for (int i = 0; i < array3d.length; i++) { + for (int j = 0; j < array3d[i].length; j++) { + for (int k = 0; k < array3d[i][j].length; k++) { + System.out.print(array3d[i][j][k] + " "); + } + System.out.println(); + } + System.out.println("-----"); + } + System.out.println(array3d[1][1][1]); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java new file mode 100644 index 0000000..24aa637 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java @@ -0,0 +1,36 @@ +package com.wdbyte; + +/** + * @author niulang + * @date 2023/03/23 + */ +public class JavaDataType { + + public static void main(String[] args) { + boolean result = true; + char capitalC = 'C'; + byte b = 100; + short s = 10000; + int i = 100000; + + // 10 进制的 26 + int decVal = 26; + // 16 进制的 26 + int hexVal = 0x1a; + // 2 进制的 26 + int binVal = 0b11010; + System.out.println(decVal); + System.out.println(hexVal); + System.out.println(binVal); + + long creditCardNumber = 1234_5678_9012_3456L; + long socialSecurityNumber = 999_99_9999L; + float pi = 3.14_15F; + long hexBytes = 0xFF_EC_DE_5E; + long hexWords = 0xCAFE_BABE; + long maxLong = 0x7fff_ffff_ffff_ffffL; + byte nybbles = 0b0010_0101; + long bytes = 0b11010010_01101001_10010100_10010010; + + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java new file mode 100644 index 0000000..31890ee --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java @@ -0,0 +1,68 @@ +package com.wdbyte; + +/** + * @author niulang + * @date 2023/03/22 + */ +public class JavaString { + public static void main(String[] args) { + //String str1 = "Hello world"; + //String str2 = "Hello world"; + //String str3 = new String("Hello world"); + //String str4 = new String("Hello world"); + // + //System.out.println(str1 == str2); + //System.out.println(str3 == str4); + //System.out.println(str1 == str4); + // 比较 + //String str1 = "abc"; + //String str2 = "abz"; + //System.out.println(str1.compareTo(str2)); + //System.out.println(str2.compareTo(str1)); + //// 输出:-23 + //// 输出:23 + //System.out.println("19999".compareTo("2")); + //// 输出:-1 + + // + //// CharSequence + //String str = "Hello, world!"; + //str.chars().forEach(System.out::println); + //str.chars().mapToObj(c -> (char) c).forEach(System.out::print); + + //String str1 = "Hello"; + //String str2 = "world"; + //String str3 = str1 + str2; + //String str4 = str1 + str2; + //String str5 = "Hello" + "world"; + //String str6 = "Hello" + "world"; + //System.out.println(str3 == str4); + //System.out.println(str5 == str6); + + //String str1 = new StringBuilder("计算机").append("软件").toString(); + //System.out.println(str1.intern() == str1); + //String str2 = new StringBuilder("ja").append("va").toString(); + //System.out.println(str2.intern() == str2); + // + //String b = "12"; + //String d = new StringBuilder("计算机").append("软件").toString(); + //String c = new StringBuilder("计算机").append("软件").toString(); + //String a = "计算机软件"; + //System.out.println(a == d); + //System.out.println(a == c); + String str0 = "abc"; + String str1 = new String("abc"); + String str3 = "abc"; + System.out.println(str1 == str0); + System.out.println(str3 == str0); + str1.intern(); + String str2 = new String("Hello,world"); + System.out.println(str1 == str2); + } + + public void test() { + String str1 = "Hello, world!"; + String str2 = "Hello, world!"; + System.out.println(str1 == str2); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java new file mode 100644 index 0000000..b492031 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java @@ -0,0 +1,38 @@ +package com.wdbyte; + +/** + * @author niulang + * @date 2023/03/30 + */ +public class JavaStringBuilder { + + public static void main(String[] args) { + StringBuilder sb1 = new StringBuilder(); + System.out.println(sb1.capacity()); // 容量:16 + StringBuilder sb2 = new StringBuilder("wdbyte.com"); + System.out.println(sb2.capacity()); // 容量:26 + + StringBuilder sb3 = new StringBuilder("www"); + sb3.append(".wdbyte.com"); + System.out.println(sb3.toString()); // + + StringBuilder sb4 = new StringBuilder("wdbyte"); + sb4.insert(0,"www."); + System.out.println(sb4.toString()); // www.wdbyte + sb4.insert(10,".com"); + System.out.println(sb4.toString()); // www.wdbyte.com + + StringBuilder sb5 = new StringBuilder("www.wdbyte.com"); + sb5.delete(0,4); + System.out.println(sb5); // wdbyte.com + + StringBuilder sb6 = new StringBuilder("hello world!"); + sb6.replace(6,11, "java"); // 结果为 "hello java!" + System.out.println(sb6); + + StringBuilder sb7 = new StringBuilder("hello world!"); + sb7.reverse(); + System.out.println(sb7); + + } +} diff --git a/core-java-modules/core-java-collect/pom.xml b/core-java-modules/core-java-collect/pom.xml new file mode 100644 index 0000000..0e0ba53 --- /dev/null +++ b/core-java-modules/core-java-collect/pom.xml @@ -0,0 +1,20 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + + core-java-collect + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 80bd3fc..55a59a8 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -26,6 +26,8 @@ core-java-io core-java-string core-java-18 + core-java-collect + core-java-base \ No newline at end of file diff --git a/tool-java-hotcode/.gitignore b/tool-java-hotcode/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/tool-java-hotcode/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file From 76fbbc46d40097d748a77807c5b556097fce1a70 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 30 Mar 2023 23:02:13 +0800 Subject: [PATCH 045/105] update readme --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4bff109..61447c1 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 未读代码

-目录中没有链接的部分,后续更新,感谢你的 ​STAR​ ,有问题或者建议可以[一起完善](https://github.com/niumoo/JavaNotes#-%E8%B4%A1%E7%8C%AE%E4%B8%8E%E5%BB%BA%E8%AE%AE)。 +目录中没有链接的部分,后续更新,感谢你的 ​STAR​ ,有问题或者建议可以[一起完善](Accept#-%E8%B4%A1%E7%8C%AE%E4%B8%8E%E5%BB%BA%E8%AE%AE)。 文章内容也都可以访问网站 [https://www.wdbyte.com](https://www.wdbyte.com) 进行阅读。 @@ -31,11 +31,18 @@ - [「1024」专属序猿的快乐,惊奇迷惑代码大赏](https://www.wdbyte.com/2020/10/2020-1024/) - [一篇有趣的负载均衡算法实现](https://www.wdbyte.com/2020/05/algorithm/load-balancing/) - [撸了个多线程断点续传下载器,我从中学习到了这些知识](https://www.wdbyte.com/2020/07/tool/java-download/) - - [Java 开发的编程噩梦,这些坑你没踩过算我输](https://www.wdbyte.com/2020/08/java/java-code-standards/) - [如何使用 Lombok 进行优雅的编码](https://www.wdbyte.com/2018/12/develop/tool-lombok/) - [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/) +## 🌿 Java 基础教程 +- [JDK、JRE、JVM 的区别](https://www.wdbyte.com/java/jdk-jre-jvm.html) +- [Java 数据类型](https://www.wdbyte.com/java/data-type.html) +- [Java 流程控制](https://www.wdbyte.com/java/flow-control.html) +- [Java String 字符串](https://www.wdbyte.com/java/java-string.html) +- [Java Array 数组](https://www.wdbyte.com/java/java-array.html) +- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul.html) +- [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder.html) ## 🌿 SpringBoot 2.x 教程 From d5dc3ebd7e3d95bd6ba52ddb8acd5a0be084396a Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 13 Apr 2023 19:23:07 +0800 Subject: [PATCH 046/105] =?UTF-8?q?feat:=20Java=20=E7=BB=A7=E6=89=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++-- .../main/java/com/wdbyte/oop/JavaExtends.java | 66 +++++++++++++++++++ 2 files changed, 74 insertions(+), 7 deletions(-) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java diff --git a/README.md b/README.md index 61447c1..01c2818 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,14 @@ - [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/) ## 🌿 Java 基础教程 -- [JDK、JRE、JVM 的区别](https://www.wdbyte.com/java/jdk-jre-jvm.html) -- [Java 数据类型](https://www.wdbyte.com/java/data-type.html) -- [Java 流程控制](https://www.wdbyte.com/java/flow-control.html) -- [Java String 字符串](https://www.wdbyte.com/java/java-string.html) -- [Java Array 数组](https://www.wdbyte.com/java/java-array.html) -- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul.html) -- [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder.html) +- [JDK、JRE、JVM 的区别](https://www.wdbyte.com/java/jdk-jre-jvm/) +- [Java 数据类型](https://www.wdbyte.com/java/data-type/) +- [Java 流程控制](https://www.wdbyte.com/java/flow-control/) +- [Java String 字符串](https://www.wdbyte.com/java/java-string/) +- [Java Array 数组](https://www.wdbyte.com/java/java-array/) +- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/) +- [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/) +- [Java 继承](https://www.wdbyte.com/java/extends/) ## 🌿 SpringBoot 2.x 教程 diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java new file mode 100644 index 0000000..595a36c --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java @@ -0,0 +1,66 @@ +package com.wdbyte.oop; + +/** + * @author niulang + * @date 2023/03/31 + */ +public class JavaExtends { + public static void main(String[] args) { + Student student = new Student(); + printSleep(student); + } + public static void printSleep(Person person){ + person.sleep(); + } +} + +class Person { + public void eat() { + System.out.println("吃饭"); + } + + public void sleep() { + System.out.println("睡觉"); + } +} + +class Student extends Person { + + public Student() { + super(); + } + + public void study() { + System.out.println("学习"); + } + + @Override + public void sleep() { + System.out.println("上课时不能睡觉"); + } +} + +interface One { + public void print_geek(); +} + +interface Two { + public void print_for(); +} + +interface Three extends One, Two { + public void print_geek(); +} + +class Teach implements Three{ + + @Override + public void print_for() { + + } + + @Override + public void print_geek() { + + } +} \ No newline at end of file From 2cc614a32cb73b9d76bb1951a00b4761d9af6f8d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Sat, 15 Apr 2023 08:41:01 +0800 Subject: [PATCH 047/105] =?UTF-8?q?feat:=20Java=20=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + core-java-modules/core-java-base/pom.xml | 7 ++ .../com/wdbyte/{ => array}/JavaArray.java | 2 +- .../java/com/wdbyte/array/JavaArray2.java | 32 +++++++++ .../com/wdbyte/{ => array}/JavaArray3.java | 2 +- .../java/com/wdbyte/array/JavaArray4.java | 42 ++++++++++++ .../main/java/com/wdbyte/oop/JavaExtends.java | 5 ++ .../java/com/wdbyte/oop/JavaInterface.java | 53 +++++++++++++++ .../java/com/wdbyte/oop/JavaInterface2.java | 66 +++++++++++++++++++ .../java/com/wdbyte/oop/JavaInterface3.java | 15 +++++ 10 files changed, 223 insertions(+), 2 deletions(-) rename core-java-modules/core-java-base/src/main/java/com/wdbyte/{ => array}/JavaArray.java (98%) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java rename core-java-modules/core-java-base/src/main/java/com/wdbyte/{ => array}/JavaArray3.java (96%) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java diff --git a/README.md b/README.md index 01c2818..eed626f 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ - [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/) - [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/) - [Java 继承](https://www.wdbyte.com/java/extends/) +- [Java 接口](https://www.wdbyte.com/java/interface/) ## 🌿 SpringBoot 2.x 教程 diff --git a/core-java-modules/core-java-base/pom.xml b/core-java-modules/core-java-base/pom.xml index da2f162..b5a436e 100644 --- a/core-java-modules/core-java-base/pom.xml +++ b/core-java-modules/core-java-base/pom.xml @@ -9,4 +9,11 @@ core-java-base Archetype - core-java-base http://maven.apache.org + + + 17 + 17 + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java similarity index 98% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java index f82fd63..daac4ec 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java @@ -1,4 +1,4 @@ -package com.wdbyte; +package com.wdbyte.array; import java.util.ArrayList; import java.util.Arrays; diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java new file mode 100644 index 0000000..b5a5b66 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java @@ -0,0 +1,32 @@ +package com.wdbyte.array; + +/** + * @author niulang + * @date 2023/03/25 + */ +public class JavaArray2 { + + public static void main(String[] args) { + int[][] myArray = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; + for (int i = 0; i < myArray.length; i++) { + for (int j = 0; j < myArray[i].length; j++) { + System.out.print(myArray[i][j] + " "); + } + System.out.println(); + } + System.out.println(myArray[0][0]); // 输出1 + System.out.println(myArray[0][1]); // 输出2 + System.out.println(myArray[1][0]); // 输出4 + + System.out.println("------------"); + + int intArray[][] = { { 1, 2, 3 }, { 4, 5 } }; + int cloneArray[][] = intArray.clone(); + // 输出:false + System.out.println(intArray == cloneArray); + + // 因为是浅拷贝,所以元素引用相同,输出 true + System.out.println(intArray[0] == cloneArray[0]); + System.out.println(intArray[1] == cloneArray[1]); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java similarity index 96% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java index cf6c175..d48cd29 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaArray3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java @@ -1,4 +1,4 @@ -package com.wdbyte; +package com.wdbyte.array; /** * @author niulang diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java new file mode 100644 index 0000000..b1b998c --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java @@ -0,0 +1,42 @@ +package com.wdbyte.array; + +/** + * 锯齿数组 + * @author niulang + * @date 2023/03/25 + */ +public class JavaArray4 { + + public static void main(String[] args) { + int[][] arrName1 = new int[][] { + new int[] {10, 20, 30, 40}, + new int[] {50, 60, 70, 80, 90, 100}, + new int[] {110, 120} + }; + + int[][] arrName2 = { + new int[] {10, 20, 30, 40}, + new int[] {50, 60, 70, 80, 90, 100}, + new int[] {110, 120} + }; + + int[][] arrName3 = { + {10, 20, 30, 40}, + {50, 60, 70, 80, 90, 100}, + {110, 120} + }; + + int[][] jaggedArray = new int[3][]; + jaggedArray[0] = new int[] {1, 2, 3}; + jaggedArray[1] = new int[] {4, 5}; + jaggedArray[2] = new int[] {6, 7, 8, 9}; + + for (int i = 0; i < jaggedArray.length; i++) { + for (int j = 0; j < jaggedArray[i].length; j++) { + System.out.print(jaggedArray[i][j] + " "); + } + System.out.println(); + } + + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java index 595a36c..7811d7f 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java @@ -34,6 +34,11 @@ public void study() { System.out.println("学习"); } + @Override + public void eat() { + System.out.println("吃大米"); + } + @Override public void sleep() { System.out.println("上课时不能睡觉"); diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java new file mode 100644 index 0000000..afcfc2b --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java @@ -0,0 +1,53 @@ +package com.wdbyte.oop; + +/** + * @author niulang + * @date 2023/04/13 + */ +public class JavaInterface { + + public static void main(String[] args) { + MyInterface.staticMethod(); + System.out.println(MyInterface.param); + } +} + +interface MyInterface { + String param = null; + void method1(); + void method2(); + // 默认方法 + default void defaultMethod() { + System.out.println("default method"); + } + // 私有方法 + private void privateMethod() { + System.out.println("private method"); + } + // 静态方法 + static void staticMethod() { + System.out.println("static method"); + } + // 私有静态方法 + private static void privateStaticMethod() { + System.out.println("private static method"); + } +} +interface IMobilePhone{ + void mehtod1(); +} +interface MyInterface2{ + void mehtod2(); +} +class IPhone14 implements IMobilePhone{ + @Override + public void mehtod1() { + } + +} +class Xiaomi14 implements IMobilePhone{ + @Override + public void mehtod1() { + } + +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java new file mode 100644 index 0000000..3326b66 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java @@ -0,0 +1,66 @@ +package com.wdbyte.oop; + +/** + * @author niulang + * @date 2023/04/13 + */ +public class JavaInterface2 { + public static void main(String[] args) { + //XPhone xPhone = new XPhone(); + //xPhone.powerUp(); + //xPhone.call(); + } +} + + + +/** + * 手机系统功能接口 + */ +interface IMobilePhoneSystem{ + // 开机 + void powerUp(); + // 显示 + void display(); + // 声音 + void sound(); +} + +/** + * 手机基本功能 + */ +interface IMobilePhoneBasicFunction extends IMobilePhoneSystem { + // 打电话 + void call(); + // 发短信 + void sendMessage(); +} + + +class XPhone implements IMobilePhoneBasicFunction{ + + @Override + public void powerUp() { + System.out.println("XPhone 开始开机"); + } + + @Override + public void display() { + System.out.println("XPhone 开始显示"); + } + + @Override + public void sound() { + System.out.println("XPhone 发出声音"); + } + + @Override + public void call() { + System.out.println("XPhone 开始拨打电话"); + } + + @Override + public void sendMessage() { + System.out.println("XPhone 开始发送短信"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java new file mode 100644 index 0000000..86282a6 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java @@ -0,0 +1,15 @@ +package com.wdbyte.oop; + +/** + * @author niulang + * @date 2023/04/14 + */ +public class JavaInterface3 { + public static void main(String[] args) { + System.out.println(MyInterface.param); + } + interface MyInterface{ + String param = "https://www.wdbyte.com"; + } +} + From 014a6838f86c4897dc21b1fc4b665f6c5d7c206f Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 18 Apr 2023 14:04:26 +0800 Subject: [PATCH 048/105] =?UTF-8?q?feat:=20Java=20=E6=8A=BD=E8=B1=A1?= =?UTF-8?q?=E7=B1=BB=EF=BC=8CJava=20=E5=A4=9A=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wdbyte/oop/abs/AbsPerson.java | 15 ++++++++++ .../java/com/wdbyte/oop/abs/PersonTest.java | 23 +++++++++++++++ .../main/java/com/wdbyte/oop/abs/Student.java | 13 +++++++++ .../main/java/com/wdbyte/oop/abs/Teacher.java | 13 +++++++++ .../oop/{ => interfac}/JavaInterface.java | 2 +- .../oop/{ => interfac}/JavaInterface2.java | 2 +- .../oop/{ => interfac}/JavaInterface3.java | 2 +- .../wdbyte/oop/polymorphism/AliyunOss.java | 17 +++++++++++ .../com/wdbyte/oop/polymorphism/Animal.java | 17 +++++++++++ .../java/com/wdbyte/oop/polymorphism/Cat.java | 8 ++++++ .../java/com/wdbyte/oop/polymorphism/Dog.java | 8 ++++++ .../java/com/wdbyte/oop/polymorphism/Oss.java | 14 ++++++++++ .../com/wdbyte/oop/polymorphism/OssUtil.java | 28 +++++++++++++++++++ .../wdbyte/oop/polymorphism/TencentOss.java | 17 +++++++++++ .../wdbyte/oop/polymorphism/inter/Animal.java | 7 +++++ .../wdbyte/oop/polymorphism/inter/Cat.java | 8 ++++++ .../wdbyte/oop/polymorphism/inter/Dog.java | 8 ++++++ .../wdbyte/oop/polymorphism/inter/Test.java | 23 +++++++++++++++ .../wdbyte/oop/polymorphism/inter/Test2.java | 20 +++++++++++++ 19 files changed, 242 insertions(+), 3 deletions(-) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java rename core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/{ => interfac}/JavaInterface.java (96%) rename core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/{ => interfac}/JavaInterface2.java (97%) rename core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/{ => interfac}/JavaInterface3.java (88%) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Animal.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Cat.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Dog.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Animal.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Cat.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Dog.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java new file mode 100644 index 0000000..062bb70 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java @@ -0,0 +1,15 @@ +package com.wdbyte.oop.abs; + +/** + * @author niulang + * @date 2023/04/17 + */ +public abstract class AbsPerson { + public int age = 22; + + public void eat() { + System.out.println("吃饭"); + } + + public abstract void sleep(); +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java new file mode 100644 index 0000000..3c8d06e --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java @@ -0,0 +1,23 @@ +package com.wdbyte.oop.abs; + +/** + * @author niulang + * @date 2023/04/17 + */ +public class PersonTest { + public static void main(String[] args) { + AbsPerson absPerson = new Student(); + System.out.println(absPerson.age); + absPerson.eat(); + absPerson.sleep(); + + Student student = new Student(); + Teacher teacher = new Teacher(); + sleep(student); + sleep(teacher); + } + + public static void sleep(AbsPerson person) { + person.sleep(); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java new file mode 100644 index 0000000..bd9060f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java @@ -0,0 +1,13 @@ +package com.wdbyte.oop.abs; + +/** + * @author niulang + * @date 2023/04/17 + */ +public class Student extends AbsPerson { + + @Override + public void sleep() { + System.out.println("学生上课不能睡觉"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java new file mode 100644 index 0000000..8c790c8 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java @@ -0,0 +1,13 @@ +package com.wdbyte.oop.abs; + +/** + * @author niulang + * @date 2023/04/17 + */ +public class Teacher extends AbsPerson { + + @Override + public void sleep() { + System.out.println("老师教课时不能睡觉"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java similarity index 96% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java index afcfc2b..7359a11 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java @@ -1,4 +1,4 @@ -package com.wdbyte.oop; +package com.wdbyte.oop.interfac; /** * @author niulang diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java similarity index 97% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java index 3326b66..b4bce89 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java @@ -1,4 +1,4 @@ -package com.wdbyte.oop; +package com.wdbyte.oop.interfac; /** * @author niulang diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java similarity index 88% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java index 86282a6..d4a75a9 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaInterface3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java @@ -1,4 +1,4 @@ -package com.wdbyte.oop; +package com.wdbyte.oop.interfac; /** * @author niulang diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java new file mode 100644 index 0000000..d6229c3 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java @@ -0,0 +1,17 @@ +package com.wdbyte.oop.polymorphism; + +/** + * @author niulang + * @date 2023/04/18 + */ +public class AliyunOss implements Oss { + @Override + public void upload(String content) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println("文件已经上传到阿里云 OSS,内容:" + content); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Animal.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Animal.java new file mode 100644 index 0000000..a0d456f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Animal.java @@ -0,0 +1,17 @@ +package com.wdbyte.oop.polymorphism; + +import java.util.Arrays; + +public class Animal { + public void makeSound() { + System.out.println("动物发出叫声"); + } + + public static void main(String[] args) { + Animal cat = new Cat(); + Animal dog = new Dog(); + for (Animal animal : Arrays.asList(cat, dog)) { + animal.makeSound(); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Cat.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Cat.java new file mode 100644 index 0000000..152e261 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Cat.java @@ -0,0 +1,8 @@ +package com.wdbyte.oop.polymorphism; + +public class Cat extends Animal{ + @Override + public void makeSound(){ + System.out.println("喵喵喵"); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Dog.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Dog.java new file mode 100644 index 0000000..ec6066a --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Dog.java @@ -0,0 +1,8 @@ +package com.wdbyte.oop.polymorphism; + +public class Dog extends Animal{ + @Override + public void makeSound(){ + System.out.println("汪汪汪"); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java new file mode 100644 index 0000000..f7466eb --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java @@ -0,0 +1,14 @@ +package com.wdbyte.oop.polymorphism; + +/** + * @author niulang + * @date 2023/04/18 + */ +public interface Oss { + + /** + * 文件上传 + * @param content + */ + void upload(String content); +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java new file mode 100644 index 0000000..1e9eeef --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java @@ -0,0 +1,28 @@ +package com.wdbyte.oop.polymorphism; + +/** + * @author niulang + * @date 2023/04/18 + */ +public class OssUtil { + /** + * OSS 上传工具类 + * + * @param oss + * @param content + */ + public static void upload(Oss oss, String content) { + long start = System.currentTimeMillis(); + oss.upload(content); + long end = System.currentTimeMillis(); + System.out.println("上传耗时:" + (end - start) + "ms"); + } + + public static void main(String[] args) { + AliyunOss aliyunOss = new AliyunOss(); + TencentOss tencentOss = new TencentOss(); + upload(aliyunOss, "Hello aliyun"); + System.out.println("------------"); + upload(tencentOss, "Hello tencent"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java new file mode 100644 index 0000000..ece1f22 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java @@ -0,0 +1,17 @@ +package com.wdbyte.oop.polymorphism; + +/** + * @author niulang + * @date 2023/04/18 + */ +public class TencentOss implements Oss { + @Override + public void upload(String content) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + System.out.println("文件已经上传到腾讯云 OSS,内容:" + content); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Animal.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Animal.java new file mode 100644 index 0000000..0353708 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Animal.java @@ -0,0 +1,7 @@ +package com.wdbyte.oop.polymorphism.inter; + +public interface Animal{ + void makeSound(); +} + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Cat.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Cat.java new file mode 100644 index 0000000..1a6e10c --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Cat.java @@ -0,0 +1,8 @@ +package com.wdbyte.oop.polymorphism.inter; + +public class Cat implements Animal{ + @Override + public void makeSound(){ + System.out.println("喵喵喵"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Dog.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Dog.java new file mode 100644 index 0000000..8843325 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Dog.java @@ -0,0 +1,8 @@ +package com.wdbyte.oop.polymorphism.inter; + +public class Dog implements Animal{ + @Override + public void makeSound(){ + System.out.println("汪汪汪"); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java new file mode 100644 index 0000000..7565cd6 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java @@ -0,0 +1,23 @@ +package com.wdbyte.oop.polymorphism.inter; + +import java.util.Arrays; + + +/** + * @author niulang + * @date 2023/04/18 + */ +public class Test { + public static void main(String[] args) { + Animal cat = new Cat(); + Animal dog = new Dog(); + for (Animal animal : Arrays.asList(cat, dog)) { + animal.makeSound(); + } + } + + public static void makeSound(Animal animal){ + animal.makeSound(); + } +} + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java new file mode 100644 index 0000000..778c203 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java @@ -0,0 +1,20 @@ +package com.wdbyte.oop.polymorphism.inter; + + +/** + * @author niulang + * @date 2023/04/18 + */ +public class Test2 { + public static void main(String[] args) { + Animal cat = new Cat(); + Animal dog = new Dog(); + makeSound(cat); // 喵喵喵 + makeSound(dog); // 汪汪汪 + } + + public static void makeSound(Animal animal) { + animal.makeSound(); + } +} + From e61accd3ad9398387c8318ce1906481efcd220d3 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 18 Apr 2023 14:05:29 +0800 Subject: [PATCH 049/105] =?UTF-8?q?feat:=20Java=20=E6=8A=BD=E8=B1=A1?= =?UTF-8?q?=E7=B1=BB=EF=BC=8CJava=20=E5=A4=9A=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eed626f..70c68c3 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,8 @@ - [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/) - [Java 继承](https://www.wdbyte.com/java/extends/) - [Java 接口](https://www.wdbyte.com/java/interface/) +- [Java 抽象类](https://www.wdbyte.com/java/abstract/) +- [Java 多态](https://www.wdbyte.com/java/polymorphism/) ## 🌿 SpringBoot 2.x 教程 From 18b86f3083a16ca56219b59a41f105378ba3baec Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 21 Apr 2023 17:09:12 +0800 Subject: [PATCH 050/105] feat: Java Scanner demo --- .../java/com/wdbyte/scanner/ScannerDemo.java | 13 +++++++++++++ .../java/com/wdbyte/scanner/ScannerDemo2.java | 16 ++++++++++++++++ .../java/com/wdbyte/scanner/ScannerDemo3.java | 15 +++++++++++++++ 3 files changed, 44 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo3.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo.java new file mode 100644 index 0000000..a2de8cc --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo.java @@ -0,0 +1,13 @@ +package com.wdbyte.scanner; + +import java.util.Scanner; + +public class ScannerDemo { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + scanner.useDelimiter(System.lineSeparator()); + System.out.println("请输入一个字符串:"); + String inputString = scanner.next(); + System.out.println("你输入的字符串是:" + inputString); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo2.java new file mode 100644 index 0000000..dc6337e --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo2.java @@ -0,0 +1,16 @@ +package com.wdbyte.scanner; + +import java.util.Scanner; + +public class ScannerDemo2 { + public static void main(String[] args) { + Scanner scanner = new Scanner(System.in); + System.out.println("请输入一个整数:"); + int inputInt = scanner.nextInt(); + System.out.println("你输入的整数是:" + inputInt); + + System.out.println("请输入一个浮点数:"); + double inputDouble = scanner.nextDouble(); + System.out.println("你输入的浮点数是:" + inputDouble); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo3.java new file mode 100644 index 0000000..d701062 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/scanner/ScannerDemo3.java @@ -0,0 +1,15 @@ +package com.wdbyte.scanner; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.Scanner; + +public class ScannerDemo3 { + public static void main(String[] args) throws FileNotFoundException { + File inputFile = new File("pom.xml"); + Scanner scanner = new Scanner(inputFile); + while (scanner.hasNextLine()) { + System.out.println(scanner.nextLine()); + } + } +} \ No newline at end of file From ff999a980969d16b60856b1476fdeca79a183231 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Thu, 27 Apr 2023 10:24:04 +0800 Subject: [PATCH 051/105] feat: add java date demo --- README.md | 2 + .../java/com/wdbyte/date/JavaDateCalc.java | 68 +++++++++++++++++++ .../java/com/wdbyte/date/JavaDateCalc2.java | 24 +++++++ .../java/com/wdbyte/date/JavaDateCreate.java | 22 ++++++ .../java/com/wdbyte/date/JavaDateDiff.java | 54 +++++++++++++++ .../java/com/wdbyte/date/JavaDateFormat.java | 32 +++++++++ 6 files changed, 202 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java diff --git a/README.md b/README.md index 70c68c3..ccd4ff3 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,8 @@ - [Java 接口](https://www.wdbyte.com/java/interface/) - [Java 抽象类](https://www.wdbyte.com/java/abstract/) - [Java 多态](https://www.wdbyte.com/java/polymorphism/) +- [Java Scanner](https://www.wdbyte.com/java/scanner/) +- [Java 日期时间 Date](https://www.wdbyte.com/java/date/) ## 🌿 SpringBoot 2.x 教程 diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java new file mode 100644 index 0000000..e400cdb --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java @@ -0,0 +1,68 @@ +package com.wdbyte.date; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * @author niulang + * @date 2023/04/26 + */ +public class JavaDateCalc { + public static void main(String[] args) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + Date date = new Date(); + System.out.println(String.format("当前时间:%s", sdf.format(date))); + date = new JavaDateCalc().addDay(date, 3); + System.out.println(String.format("增加三天:%s", sdf.format(date))); + date = new JavaDateCalc().minDay(date, 1); + System.out.println(String.format("减去一天:%s", sdf.format(date))); + date = new JavaDateCalc().addMonth(date, 2); + System.out.println(String.format("增加两个月:%s", sdf.format(date))); + } + + /** + * 增加天数 + * + * @param date + * @param addDay + * @return + */ + public Date addDay(Date date, int addDay) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DATE, addDay); + Date newDate = cal.getTime(); + return newDate; + } + + /** + * 减少天数 + * + * @param date + * @param minDay + * @return + */ + public Date minDay(Date date, int minDay) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.DATE, -minDay); + Date newDate = cal.getTime(); + return newDate; + } + + /** + * 增加月份 + * + * @param date + * @param addMonth + * @return + */ + public Date addMonth(Date date, int addMonth) { + Calendar cal = Calendar.getInstance(); + cal.setTime(date); + cal.add(Calendar.MONTH, addMonth); + Date newDate = cal.getTime(); + return newDate; + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java new file mode 100644 index 0000000..16f0c4a --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java @@ -0,0 +1,24 @@ +package com.wdbyte.date; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; + +/** + * @author niulang + * @date 2023/04/26 + */ +public class JavaDateCalc2 { + public static void main(String[] args) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + Date date = new Date(); + System.out.println(String.format("当前时间:%s", sdf.format(date))); + // 三天的毫秒数:3 * 24 * 3600 * 1000 + date = new Date(date.getTime() + 3 * 24 * 3600 * 1000); + System.out.println(String.format("增加三天:%s", sdf.format(date))); + + date = new Date(date.getTime() - 1 * 60 * 1000); + System.out.println(String.format("减去一分钟:%s", sdf.format(date))); + } + +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java new file mode 100644 index 0000000..2046ffe --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java @@ -0,0 +1,22 @@ +package com.wdbyte.date; + +import java.util.Date; + +/** + * @author niulang + * @date 2023/04/25 + */ +public class JavaDateCreate { + public static void main(String[] args) { + Date date = new Date(); + // 输出时间 + System.out.println(date); // Tue Apr 25 20:28:23 CST 2023 + // 输出毫秒数 + System.out.println(date.getTime()); // 1682425703429 + + // 当前毫秒数创建对象 + //Date date2 = new Date(System.currentTimeMillis()); + Date date2 = new Date(1682425703429L); + System.out.println(date2); // Tue Apr 25 20:28:23 CST 2023 + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java new file mode 100644 index 0000000..2f47ade --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java @@ -0,0 +1,54 @@ +package com.wdbyte.date; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author niulang + * @date 2023/04/25 + */ +public class JavaDateDiff { + + public static void main(String[] args) throws InterruptedException { + // 获取当前时间 + Date date1 = new Date(); + // 休眠 3 秒 + Thread.sleep(3000); + // 再获取一次当前时间 + Date date2 = new Date(); + + // 很明显 date1 在 date2 之前,,所以 true + boolean before = date1.before(date2); + System.out.println(before); // true + + // 很明显 date2 在 date1 之后,所以 true + boolean after = date2.after(date1); + System.out.println(after); // true + + // date2 不在 date1 之前,所以 false + System.out.println(date2.before(date1)); // false + + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + System.out.println("date1:"+sdf.format(date1)); + System.out.println("date2:"+sdf.format(date2)); + Long diff = date1.getTime() - date2.getTime(); + if (diff > 0) { + System.out.println("date1 > date2"); + } + if (diff < 0) { + System.out.println("date1 < date2"); + } + if (diff == 0) { + System.out.println("date1 = date2"); + } + exec(); + } + + public static void exec() throws InterruptedException { + long start = System.currentTimeMillis(); + // 做点什么 + Thread.sleep(3000); + long end = System.currentTimeMillis(); + System.out.println("耗时:" + (end - start) + "ms"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java new file mode 100644 index 0000000..47be812 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java @@ -0,0 +1,32 @@ +package com.wdbyte.date; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author niulang + * @date 2023/04/25 + */ +public class JavaDateFormat { + + public static void main(String[] args) throws ParseException { + Date date = new Date(); + + // 时间格式化,yyyy 年份,MM 月份, dd 当月第多少天, hh:mm:ss 分别为时分秒 + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + String formatDate = simpleDateFormat.format(date); + System.out.println(formatDate); + + SimpleDateFormat sdf1 = new SimpleDateFormat("当前日期是: yyyy-MM-dd"); + SimpleDateFormat sdf2 = new SimpleDateFormat("当前时间是: hh:mm:ss"); + System.out.println(sdf1.format(date)); + System.out.println(sdf2.format(date)); + + + String strDate = "2023-01-19 10:30:00"; + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); + Date myDate = sdf.parse(strDate); + System.out.println(sdf.format(myDate)); + } +} From aee250d8c70ee8a9df56d58ab85af0213fe11a38 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 28 Apr 2023 16:20:55 +0800 Subject: [PATCH 052/105] feat: add java exception --- README.md | 1 + .../com/wdbyte/exception/JavaException1.java | 12 +++++ .../com/wdbyte/exception/JavaException2.java | 13 ++++++ .../com/wdbyte/exception/JavaException3.java | 19 ++++++++ .../com/wdbyte/exception/JavaException4.java | 19 ++++++++ .../com/wdbyte/exception/JavaException5.java | 35 +++++++++++++++ .../com/wdbyte/exception/JavaException6.java | 20 +++++++++ .../com/wdbyte/exception/JavaException7.java | 22 +++++++++ .../com/wdbyte/exception/JavaException8.java | 33 ++++++++++++++ .../com/wdbyte/exception/MyException.java | 7 +++ .../java/com/wdbyte/exception/ReadFile.java | 45 +++++++++++++++++++ .../java/com/wdbyte/exception/ReadFile2.java | 25 +++++++++++ 12 files changed, 251 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/MyException.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile2.java diff --git a/README.md b/README.md index ccd4ff3..c873bb5 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ - [Java 多态](https://www.wdbyte.com/java/polymorphism/) - [Java Scanner](https://www.wdbyte.com/java/scanner/) - [Java 日期时间 Date](https://www.wdbyte.com/java/date/) +- [Java 异常处理](https://www.wdbyte.com/java/exception/) ## 🌿 SpringBoot 2.x 教程 diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java new file mode 100644 index 0000000..18b22d1 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java @@ -0,0 +1,12 @@ +package com.wdbyte.exception; + +/** + * @author niulang + */ +public class JavaException1 { + + public static void main(String[] args) { + String str = null; + System.out.println(str.length()); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java new file mode 100644 index 0000000..e680d35 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java @@ -0,0 +1,13 @@ +package com.wdbyte.exception; + +/** + * @author niulang + */ +public class JavaException2 { + + public static void main(String[] args) { + String[] strArr = {"www", "wdbyte", "com"}; + System.out.println(strArr[0]); + System.out.println(strArr[3]); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java new file mode 100644 index 0000000..4395064 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java @@ -0,0 +1,19 @@ +package com.wdbyte.exception; + +/** + * @author niulang + */ +public class JavaException3 { + + public static void main(String[] args) { + String[] strArr = {"www", "wdbyte", "com"}; + try { + System.out.println(strArr[0]); + System.out.println(strArr[3]); + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println("发生数组访问越界异常,不能访问超过数组长度的元素,数组长度为:" + strArr.length); + System.out.println("异常描述:" + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java new file mode 100644 index 0000000..a88f5e5 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java @@ -0,0 +1,19 @@ +package com.wdbyte.exception; + +/** + * @author niulang + */ +public class JavaException4 { + + public static void main(String[] args) { + String[] strArr = {"www", "wdbyte", "com"}; + try { + System.out.println(strArr[0]); + System.out.println(strArr[3]); + } catch (ArrayIndexOutOfBoundsException e) { + //throw e; + throw new RuntimeException(e); + } + System.out.println("执行完成。"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java new file mode 100644 index 0000000..acbb901 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java @@ -0,0 +1,35 @@ +package com.wdbyte.exception; + +import org.apache.commons.lang3.ObjectUtils.Null; + +/** + * @author niulang + */ +public class JavaException5 { + + public static void main(String[] args) { + String[] strArr = {"www", null, "com"}; + try { + System.out.println(strArr[0].length()); + // 空指针异常 + System.out.println(strArr[1].length()); + System.out.println(strArr[3].length()); + } catch (ArrayIndexOutOfBoundsException e) { + System.out.println("发生数组访问越界异常,不能访问超过数组长度的元素,数组长度为:" + strArr.length); + } catch (NullPointerException e) { + System.out.println("发现空指针异常"); + } + System.out.println("执行完成。"); + + try { + System.out.println(strArr[0].length()); + // 空指针异常 + System.out.println(strArr[1].length()); + // 越界异常 + System.out.println(strArr[3].length()); + } catch (Exception e) { + System.out.println("发生异常:" + e.getMessage()); + e.printStackTrace(); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java new file mode 100644 index 0000000..4cbff20 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java @@ -0,0 +1,20 @@ +package com.wdbyte.exception; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author niulang + * @date 2023/04/27 + */ +public class JavaException6 { + + public static void main(String[] args) { + try { + Files.readAllLines(Paths.get("c:/abc.txt")); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java new file mode 100644 index 0000000..9b5483d --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java @@ -0,0 +1,22 @@ +package com.wdbyte.exception; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * @author niulang + * @date 2023/04/27 + */ +public class JavaException7 { + + public static void main(String[] args) { + try { + Files.readAllLines(Paths.get("c:/abc.txt")); + } catch (IOException e) { + e.printStackTrace(); + } finally { + + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java new file mode 100644 index 0000000..905a2ab --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java @@ -0,0 +1,33 @@ +package com.wdbyte.exception; + +/** + * @author niulang + */ +public class JavaException8 { + + public static void main(String[] args) { + int result = 0; + try { + result = calc(4, 2); + System.out.println("4 / 2 = " + result); + } catch (MyException e) { // 不处理异常会报错 + e.printStackTrace(); + } + + try { + result = calc(4, 0); + System.out.println("4 / 0 = " + result); + } catch (MyException e) { // 不处理异常会报错 + e.printStackTrace(); + } + } + + public static int calc(int a, int b) throws MyException { + if (b == 0) { + throw new MyException("除数不能为0"); + } + return a / b; + } +} + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/MyException.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/MyException.java new file mode 100644 index 0000000..a91c4a2 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/MyException.java @@ -0,0 +1,7 @@ +package com.wdbyte.exception; + +public class MyException extends Exception { + public MyException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile.java new file mode 100644 index 0000000..664cfc3 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile.java @@ -0,0 +1,45 @@ +package com.wdbyte.exception; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +public class ReadFile { + + public static void main(String[] args) { + + File file = new File("pom2.xml"); + FileReader fileReader = null; + BufferedReader bufferedReader = null; + try { + fileReader = new FileReader(file); + bufferedReader = new BufferedReader(fileReader); + String line; + while ((line = bufferedReader.readLine()) != null) { + System.out.println(line); + } + bufferedReader.close(); + fileReader.close(); + } catch (FileNotFoundException e) { + System.out.println("文件不存在"); + } catch (IOException e) { + System.out.println("读取文件失败"); + } finally { + try { + System.out.println("开始关闭资源"); + if (bufferedReader != null) { + bufferedReader.close(); + System.out.println("关闭 bufferedReader"); + } + if (fileReader != null) { + fileReader.close(); + System.out.println("关闭 fileReader"); + } + } catch (IOException e) { + System.out.println("关闭文件失败"); + } + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile2.java new file mode 100644 index 0000000..d238afb --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/ReadFile2.java @@ -0,0 +1,25 @@ +package com.wdbyte.exception; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; + +public class ReadFile2 { + + public static void main(String[] args) { + File file = new File("pom.xml"); + try (FileReader fileReader = new FileReader(file); BufferedReader bufferedReader = new BufferedReader( + fileReader);) { + String line; + while ((line = bufferedReader.readLine()) != null) { + System.out.println(line); + } + } catch (FileNotFoundException e) { + System.out.println("文件不存在"); + } catch (IOException e) { + System.out.println("读取文件失败"); + } + } +} From 4ab63d267bc70990522ff0debaec082f595e253d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 3 May 2023 17:35:36 +0800 Subject: [PATCH 053/105] feat: add java enum demo --- .../src/main/java/com/wdbyte/enum2/Calc.java | 30 ++++++++++++++++++ .../main/java/com/wdbyte/enum2/CalcTest.java | 19 ++++++++++++ .../main/java/com/wdbyte/enum2/Rectangle.java | 28 +++++++++++++++++ .../java/com/wdbyte/enum2/WeekdayTest.java | 31 +++++++++++++++++++ 4 files changed, 108 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Calc.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Rectangle.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Calc.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Calc.java new file mode 100644 index 0000000..d42ac62 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Calc.java @@ -0,0 +1,30 @@ +package com.wdbyte.enum2; + +/** + * 计算枚举类 + */ +public enum Calc { + // 加法 + PLUS { + public int apply(int x, int y) { + return x + y; + } + }, + // 减法 + MINUS { + public int apply(int x, int y) { + return x - y; + } + }, + // 乘法 + MULTIPLY { + public int apply(int x, int y) { + return x * y; + } + }; + + public int apply(int x, int y){ + // todo + return x + y; + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java new file mode 100644 index 0000000..b9eeff9 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java @@ -0,0 +1,19 @@ +package com.wdbyte.enum2; + +/** + * @author niulang + * @date 2023/05/01 + */ +public class CalcTest { + public static void main(String[] args) { + // 加法 + int res = Calc.PLUS.apply(2, 3); + System.out.println(res); + // 减法 + res = Calc.MINUS.apply(2, 3); + System.out.println(res); + // 乘法 + res = Calc.MULTIPLY.apply(2, 3); + System.out.println(res); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Rectangle.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Rectangle.java new file mode 100644 index 0000000..43ba0ac --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Rectangle.java @@ -0,0 +1,28 @@ +package com.wdbyte.enum2; + +/** + * 矩形枚举类 + */ +public enum Rectangle implements Shape { + SMALL(3, 4), + MEDIUM(4, 5), + LARGE(5, 6); + + private int length; + private int width; + + Rectangle(int length, int width) { + this.length = length; + this.width = width; + } + + public double getArea() { + return length * width; + } + + public static void main(String[] args) { + Shape shape = Rectangle.LARGE; + double shapeArea = shape.getArea(); + System.out.println(shapeArea); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java new file mode 100644 index 0000000..0478277 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java @@ -0,0 +1,31 @@ +package com.wdbyte.enum2; + +/** + * @author niulang + * @date 2023/05/01 + */ +public class WeekdayTest { + public static void main(String[] args) { + Weekday day = Weekday.MONDAY; + if (day == Weekday.MONDAY) { + System.out.println("Today is Monday."); + } + + + Weekday[] weekdays = Weekday.values(); + for (Weekday weekday : weekdays) { + System.out.println(weekday); + } + + for (Weekday weekday : weekdays) { + System.out.println(weekday.ordinal()); + } + Weekday monday = Weekday.MONDAY; + switch (monday){ + case MONDAY :{System.out.println("周一");break;} + case SUNDAY :{System.out.println("周末");break;} + } + System.out.println(Weekday.valueOf("MONDAY")); + System.out.println(Weekday.valueOf("MONDAY1")); + } +} From 0edacda1747135f2fc35841d4454a87ba4bcab49 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 3 May 2023 17:35:58 +0800 Subject: [PATCH 054/105] feat: add java collection demo --- .../com/wdbyte/collection/CollectionTest.java | 20 ++++++++++ .../wdbyte/collection/CollectionTest2.java | 35 ++++++++++++++++++ .../wdbyte/collection/CollectionTest3.java | 35 ++++++++++++++++++ .../wdbyte/collection/CollectionTest4.java | 37 +++++++++++++++++++ .../wdbyte/collection/CollectionTest5.java | 34 +++++++++++++++++ .../java/com/wdbyte/collection/MapTest.java | 21 +++++++++++ .../java/com/wdbyte/collection/MapTest2.java | 26 +++++++++++++ 7 files changed, 208 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java new file mode 100644 index 0000000..67b150a --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java @@ -0,0 +1,20 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author niulang + */ +public class CollectionTest { + public static void main(String[] args) { + List arrayList = new ArrayList<>(); + // 向 List 中添加元素 + arrayList.add("www"); + arrayList.add("wdbyte"); + arrayList.add("com"); + arrayList.add("com"); + // 输出 + System.out.println(arrayList); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java new file mode 100644 index 0000000..dfab350 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java @@ -0,0 +1,35 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.Stack; +import java.util.TreeSet; + +/** + * @author niulang + */ +public class CollectionTest2 { + public static void main(String[] args) { + List arrayList = new ArrayList<>(); + List linkedList = new LinkedList<>(); + Set hashSet = new HashSet<>(); + Set treeSet = new TreeSet<>(); + Stack stack = new Stack<>(); + // 添加元素 + arrayList.add("wdbyte.com"); + linkedList.add("wdbyte.com"); + hashSet.add("wdbyte.com"); + treeSet.add("wdbyte.com"); + stack.add("wdbyte.com"); + // 输出 + System.out.println(arrayList); + System.out.println(linkedList); + System.out.println(hashSet); + System.out.println(treeSet); + System.out.println(stack); + } + +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java new file mode 100644 index 0000000..7f75e07 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java @@ -0,0 +1,35 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +/** + * @author niulang + */ +public class CollectionTest3 { + public static void main(String[] args) { + List arrayList = new ArrayList<>(); + // 向 List 中添加元素 + arrayList.add("www"); + arrayList.add("wdbyte"); + arrayList.add("com"); + // 普通遍历 + for (int i = 0; i < arrayList.size(); i++) { + System.out.println(arrayList.get(0)); + } + System.out.println("--------"); + // iterator 迭代器遍历 + Iterator iterator = arrayList.iterator(); + while (iterator.hasNext()) { + System.out.println(iterator.next()); + } + } + + public void printCollection(Collection collection) { + for (Object o : collection) { + System.out.println(o); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java new file mode 100644 index 0000000..990fea2 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java @@ -0,0 +1,37 @@ +package com.wdbyte.collection; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +/** + * @author niulang + */ +public class CollectionTest4 { + public static void main(String[] args) { + List arrayList = new ArrayList<>(); + // 向 List 中添加元素 + arrayList.add("www"); + arrayList.add("wdbyte"); + arrayList.add("com"); + + HashSet hashSet = new HashSet<>(); + hashSet.addAll(arrayList); + + printCollection(arrayList.iterator()); + System.out.println("--------------"); + printCollection(hashSet.iterator()); + } + + public static void printCollection(Iterator iterator) { + // 当前日期 + LocalDate now = LocalDate.now(); + while (iterator.hasNext()) { + String obj = iterator.next(); + System.out.println(now + ":" + obj.toString()); + } + } + +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java new file mode 100644 index 0000000..2dc01b0 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java @@ -0,0 +1,34 @@ +package com.wdbyte.collection; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +/** + * @author niulang + */ +public class CollectionTest5 { + public static void main(String[] args) { + List arrayList = new ArrayList<>(); + // 向 List 中添加元素 + arrayList.add("www"); + arrayList.add("wdbyte"); + arrayList.add("com"); + HashSet hashSet = new HashSet<>(); + hashSet.addAll(arrayList); + + printCollection(arrayList); + printCollection(hashSet); + } + public static void printCollection(Iterable iterable) { + // 当前日期 + LocalDate now = LocalDate.now(); + Iterator iterator = iterable.iterator(); + while (iterator.hasNext()) { + String obj = iterator.next(); + System.out.println(now + ":" + obj.toString()); + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java new file mode 100644 index 0000000..b19d7b1 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java @@ -0,0 +1,21 @@ +package com.wdbyte.collection; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author niulang + */ +public class MapTest { + public static void main(String[] args) { + Map hashMap = new HashMap<>(); + // 添加元素 + hashMap.put("site","www.wdbyte.com"); + hashMap.put("author","程序猿阿朗"); + hashMap.put("github","github.com/niumoo"); + // 获取元素 + System.out.println(hashMap.get("github")); + // 输出全部元素 + System.out.println(hashMap); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java new file mode 100644 index 0000000..306e313 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java @@ -0,0 +1,26 @@ +package com.wdbyte.collection; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author niulang + */ +public class MapTest2 { + public static void main(String[] args) { + Map hashMap = new HashMap<>(); + Map linkedHashMap = new LinkedHashMap<>(); + // 添加元素 + hashMap.put("site", "www.wdbyte.com"); + hashMap.put("author", "程序猿阿朗"); + hashMap.put("github", "github.com/niumoo"); + linkedHashMap.put("site", "www.wdbyte.com"); + linkedHashMap.put("author", "程序猿阿朗"); + linkedHashMap.put("github", "github.com/niumoo"); + + // 输出全部元素 + System.out.println(hashMap); + System.out.println(linkedHashMap); + } +} From 14ed8961a317722b71127a7d54bd55a754a4e73f Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 3 May 2023 17:37:26 +0800 Subject: [PATCH 055/105] feat: add java collection demo --- README.md | 2 + ...6\345\205\263\347\263\273\345\233\276.uml" | 138 ++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 "core-java-modules/core-java-base/\351\233\206\345\220\210\346\241\206\346\236\266\345\205\263\347\263\273\345\233\276.uml" diff --git a/README.md b/README.md index c873bb5..e39a197 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ - [Java Scanner](https://www.wdbyte.com/java/scanner/) - [Java 日期时间 Date](https://www.wdbyte.com/java/date/) - [Java 异常处理](https://www.wdbyte.com/java/exception/) +- [Java 枚举](https://www.wdbyte.com/java/enum/) +- [Java 集合框架](https://www.wdbyte.com/java/collection/) ## 🌿 SpringBoot 2.x 教程 diff --git "a/core-java-modules/core-java-base/\351\233\206\345\220\210\346\241\206\346\236\266\345\205\263\347\263\273\345\233\276.uml" "b/core-java-modules/core-java-base/\351\233\206\345\220\210\346\241\206\346\236\266\345\205\263\347\263\273\345\233\276.uml" new file mode 100644 index 0000000..1e2c5c9 --- /dev/null +++ "b/core-java-modules/core-java-base/\351\233\206\345\220\210\346\241\206\346\236\266\345\205\263\347\263\273\345\233\276.uml" @@ -0,0 +1,138 @@ + + + JAVA + java.util.ArrayList + + java.util.HashSet + com.sun.jmx.remote.internal.ArrayQueue + java.util.Vector + java.util.Set + java.util.TreeSet + java.util.concurrent.ArrayBlockingQueue + java.util.List + java.util.TreeMap + java.util.concurrent.ConcurrentHashMap + java.util.LinkedHashSet + java.util.Stack + java.util.concurrent.DelayQueue + java.util.Map + java.util.LinkedList + java.lang.Iterable + java.util.AbstractList + java.util.Collection + java.util.HashMap + java.util.Queue + java.util.LinkedHashMap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + All + private + + From 15952a5811b114b270070f0898bac09fd31c2ba7 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 5 May 2023 11:55:52 +0800 Subject: [PATCH 056/105] feat: add java 20 demo --- .../core-java-20/src/JEP433SwitchTest.java | 56 +++++++++++++++++++ .../src/Jep429ScopedValueTest.java | 28 ++++++++++ .../src/Jep432RecordAndInstance.java | 22 ++++++++ .../core-java-20/src/RecordTest.java | 14 +++++ 4 files changed, 120 insertions(+) create mode 100644 core-java-modules/core-java-20/src/JEP433SwitchTest.java create mode 100644 core-java-modules/core-java-20/src/Jep429ScopedValueTest.java create mode 100644 core-java-modules/core-java-20/src/Jep432RecordAndInstance.java create mode 100644 core-java-modules/core-java-20/src/RecordTest.java diff --git a/core-java-modules/core-java-20/src/JEP433SwitchTest.java b/core-java-modules/core-java-20/src/JEP433SwitchTest.java new file mode 100644 index 0000000..8080d37 --- /dev/null +++ b/core-java-modules/core-java-20/src/JEP433SwitchTest.java @@ -0,0 +1,56 @@ +/** + * @author niulang + */ +public class JEP433SwitchTest { + public static void main(String[] args) { + Object obj = 123; + System.out.println(matchOld(obj)); // 是个数字 + System.out.println(matchNew(obj)); // 是个数字 + obj = "wdbyte.com"; + System.out.println(matchOld(obj)); // 是个字符串,长度大于2 + System.out.println(matchNew(obj)); // 是个字符串,长度大于2 + } + + /** + * 老代码 + * + * @param obj + * @return + */ + public static String matchOld(Object obj) { + if (obj == null) { + return "数据为空"; + } + if (obj instanceof String) { + String s = obj.toString(); + if (s.length() > 2) { + return "是个字符串,长度大于2"; + } + if (s.length() <= 2) { + return "是个字符串,长度小于等于2"; + } + } + if (obj instanceof Integer) { + return "是个数字"; + } + throw new IllegalStateException("未知数据:" + obj); + } + + /** + * 新代码 + * + * @param obj + * @return + */ + public static String matchNew(Object obj) { + String res = switch (obj) { + case null -> "数据为空"; + case String s when s.length() > 2 -> "是个字符串,长度大于2"; + case String s when s.length() <= 2 -> "是个字符串,长度小于等于于2"; + case Integer i -> "是个数字"; + default -> throw new IllegalStateException("未知数据:" + obj); + }; + return res; + } + +} diff --git a/core-java-modules/core-java-20/src/Jep429ScopedValueTest.java b/core-java-modules/core-java-20/src/Jep429ScopedValueTest.java new file mode 100644 index 0000000..51d03e1 --- /dev/null +++ b/core-java-modules/core-java-20/src/Jep429ScopedValueTest.java @@ -0,0 +1,28 @@ +import jdk.incubator.concurrent.ScopedValue; + +/** + * --add-modules jdk.incubator.concurrent + * + * @date 2023/05/04 + */ +public class Jep429ScopedValueTest { + final static ScopedValue SCOPED_VALUE = ScopedValue.newInstance(); + + public static void main(String[] args) { + // 创建线程 + Thread thread1 = new Thread(Jep429ScopedValueTest::handle); + Thread thread2 = new Thread(Jep429ScopedValueTest::handle); + String str = "hello wdbyte"; + // 传入线程里使用的字符串信息 + ScopedValue.where(SCOPED_VALUE, str).run(thread1); + ScopedValue.where(SCOPED_VALUE, str).run(thread2); + // 执行完毕自动清空,这里获取不到了。 + System.out.println(SCOPED_VALUE.orElse("没有信息")); + } + + public static void handle() { + String result = SCOPED_VALUE.get(); + System.out.println(result); + } + +} \ No newline at end of file diff --git a/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java b/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java new file mode 100644 index 0000000..3e732a6 --- /dev/null +++ b/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java @@ -0,0 +1,22 @@ +/** + * @author niulang + */ +public class Jep432RecordAndInstance { + public static void main(String[] args) { + ColoredPoint coloredPoint1 = new ColoredPoint(new Point(0, 0), Color.RED); + ColoredPoint coloredPoint2 = new ColoredPoint(new Point(1, 1), Color.GREEN); + Rectangle rectangle = new Rectangle(coloredPoint1, coloredPoint2); + printUpperLeftColoredPoint(rectangle); + } + + static void printUpperLeftColoredPoint(Rectangle r) { + if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { + System.out.println(ul.c()); + } + } +} + +record Point(int x, int y) {} +enum Color { RED, GREEN, BLUE } +record ColoredPoint(Point p, Color c) {} +record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} \ No newline at end of file diff --git a/core-java-modules/core-java-20/src/RecordTest.java b/core-java-modules/core-java-20/src/RecordTest.java new file mode 100644 index 0000000..ef9a7c1 --- /dev/null +++ b/core-java-modules/core-java-20/src/RecordTest.java @@ -0,0 +1,14 @@ +/** + * @author niulang + * @date 2023/05/04 + */ +public class RecordTest { + public static void main(String[] args) { + Dog dog = new Dog("name", 1); + System.out.println(dog.name()); + System.out.println(dog.age()); + } +} + +record Dog(String name, Integer age) { +} \ No newline at end of file From d16518413207182d0279adb30b2234ecdfe8a383 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 5 May 2023 13:19:06 +0800 Subject: [PATCH 057/105] feat: add java 20 demo --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e39a197..9e77cfc 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,8 @@ ## ☕ Java 新特性 Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错,何况有些新特性是**真香**。 +- [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) +- [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) - [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) - [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) - [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) From babb6fee5d12772806a0a5f0caf178d5eabb9540 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 10 May 2023 11:07:46 +0800 Subject: [PATCH 058/105] feat: update java 8 local date time --- .../java/com/wdbyte/Jdk8LocalDateTime.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java index 360e967..c41caf0 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java @@ -161,15 +161,29 @@ public void calcTest() { public void timeFunctionTest() { LocalDateTime now = LocalDateTime.now(); System.out.println("当前时间:" + now); - // 第一天 + + // LocalDateTime 本月第一天 + // 方法1 LocalDateTime firstDay = now.withDayOfMonth(1); System.out.println("本月第一天:" + firstDay); - // 当天最后一秒 - LocalDateTime lastSecondOfDay = now.withHour(23).withMinute(59).withSecond(59); - System.out.println("当天最后一秒:" + lastSecondOfDay); - // 最后一天 + // 方法2 + firstDay = now.with(TemporalAdjusters.firstDayOfMonth()); + System.out.println("本月第一天:" + firstDay); + + // LocalDateTime 本月最后一天 LocalDateTime lastDay = now.with(TemporalAdjusters.lastDayOfMonth()); System.out.println("本月最后一天:" + lastDay); + + + // LocalDateTime 当天最后一秒 + // 方法1 + LocalDateTime lastSecondOfDay1 = now.withHour(23).withMinute(59).withSecond(59); + System.out.println("当天最后一秒:" + lastSecondOfDay1); + // 方法2 + LocalDateTime lastSecondOfDay2 = LocalDateTime.now().with(LocalTime.MAX); + System.out.println("当天最后一秒:" + lastSecondOfDay2); + + // 是否闰年 System.out.println("今年是否闰年:" + Year.isLeap(now.getYear())); } From 879a101be4226fbba248d698ab31f90b5ecbe2ef Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 15:57:35 +0800 Subject: [PATCH 059/105] feat: proto buf --- pom.xml | 3 +- tool-java-protobuf/pom.xml | 45 +++++++++ .../wdbyte/tool/protos/AddressBookJava.java | 81 ++++++++++++++++ .../com/wdbyte/tool/protos/ProtobufTest1.java | 37 +++++++ .../com/wdbyte/tool/protos/ProtobufTest2.java | 35 +++++++ .../com/wdbyte/tool/protos/ProtobufTest3.java | 65 +++++++++++++ .../com/wdbyte/tool/protos/ProtobufTest4.java | 97 +++++++++++++++++++ .../com/wdbyte/tool/protos/StudentTest.java | 17 ++++ .../src/main/resources/addressbook.proto | 37 +++++++ .../src/main/resources/student.proto | 14 +++ 10 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 tool-java-protobuf/pom.xml create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookJava.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest1.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest2.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest3.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest4.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentTest.java create mode 100644 tool-java-protobuf/src/main/resources/addressbook.proto create mode 100644 tool-java-protobuf/src/main/resources/student.proto diff --git a/pom.xml b/pom.xml index 3110d06..2452dc4 100644 --- a/pom.xml +++ b/pom.xml @@ -10,13 +10,14 @@ core-java-modules core-java-rate-limiter - junit5-jupiter-starter + tool-java-junit5-jupiter-starter leetcode tool-java-apache-httpclient tool-java-object-pool tool-java-jackson tool-java-apache-common tool-java-hotcode + tool-java-protobuf parent-modules Parent for all java modules diff --git a/tool-java-protobuf/pom.xml b/tool-java-protobuf/pom.xml new file mode 100644 index 0000000..36b0b7f --- /dev/null +++ b/tool-java-protobuf/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + com.wdbyte + parent-modules + 1.0.0-SNAPSHOT + + + tool-java-protobuf + + + 1.8 + 1.8 + UTF-8 + + + + + com.google.protobuf + protobuf-java + 3.22.3 + + + + com.alibaba + fastjson + 2.0.7 + + + org.openjdk.jmh + jmh-core + 1.33 + + + org.openjdk.jmh + jmh-generator-annprocess + 1.33 + provided + + + + \ No newline at end of file diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookJava.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookJava.java new file mode 100644 index 0000000..5d500c4 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookJava.java @@ -0,0 +1,81 @@ +package com.wdbyte.tool.protos; + +import java.util.List; + +public class AddressBookJava { + List personJavaList; + + public static class PersonJava { + private int id; + private String name; + private String email; + private PhoneNumberJava phones; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public PhoneNumberJava getPhones() { + return phones; + } + + public void setPhones(PhoneNumberJava phones) { + this.phones = phones; + } + } + + public static class PhoneNumberJava { + private String number; + private PhoneTypeJava phoneTypeJava; + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public PhoneTypeJava getPhoneTypeJava() { + return phoneTypeJava; + } + + public void setPhoneTypeJava(PhoneTypeJava phoneTypeJava) { + this.phoneTypeJava = phoneTypeJava; + } + } + + public enum PhoneTypeJava { + MOBILE, + HOME, + WORK; + } + + public List getPersonJavaList() { + return personJavaList; + } + + public void setPersonJavaList(List personJavaList) { + this.personJavaList = personJavaList; + } +} \ No newline at end of file diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest1.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest1.java new file mode 100644 index 0000000..d2f825a --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest1.java @@ -0,0 +1,37 @@ +package com.wdbyte.tool.protos; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.wdbyte.tool.protos.Person.PhoneNumber; +import com.wdbyte.tool.protos.Person.PhoneType; + + +/** + * @author https://www.wdbyte.com + * @date 2023/05/07 + */ +public class ProtobufTest1 { + + public static void main(String[] args) { + // 直接构建 + PhoneNumber phoneNumber1 = PhoneNumber.newBuilder().setNumber("18388888888").setType(PhoneType.HOME).build(); + Person person1 = Person.newBuilder().setId(1).setName("www.wdbyte.com").setEmail("xxx@wdbyte.com").addPhones(phoneNumber1).build(); + AddressBook addressBook1 = AddressBook.newBuilder().addPeople(person1).build(); + System.out.println(addressBook1); + System.out.println("------------------"); + + // 链式构建 + AddressBook addressBook2 = AddressBook + .newBuilder() + .addPeople(Person.newBuilder() + .setId(2) + .setName("www.wdbyte.com") + .setEmail("yyy@126.com") + .addPhones(PhoneNumber.newBuilder() + .setNumber("18388888888") + .setType(PhoneType.HOME) + ) + ) + .build(); + System.out.println(addressBook2); + } +} diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest2.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest2.java new file mode 100644 index 0000000..0b3e236 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest2.java @@ -0,0 +1,35 @@ +package com.wdbyte.tool.protos; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import com.wdbyte.tool.protos.Person.PhoneNumber; +import com.wdbyte.tool.protos.Person.PhoneType; + +/** + * + * @author https://www.wdbyte.com + */ +public class ProtobufTest2 { + + public static void main(String[] args) throws IOException { + PhoneNumber phoneNumber1 = PhoneNumber.newBuilder().setNumber("18388888888").setType(PhoneType.HOME).build(); + Person person1 = Person.newBuilder().setId(1).setName("www.wdbyte.com").setEmail("xxx@wdbyte.com").addPhones(phoneNumber1).build(); + AddressBook addressBook1 = AddressBook.newBuilder().addPeople(person1).build(); + + // 序列化成字节数组 + byte[] byteArray = addressBook1.toByteArray(); + // 反序列化 - 字节数组转对象 + AddressBook addressBook2 = AddressBook.parseFrom(byteArray); + System.out.println("字节数组反序列化:"); + System.out.println(addressBook2); + + // 序列化到文件 + addressBook1.writeTo(new FileOutputStream("AddressBook1.txt")); + // 读取文件反序列化 + AddressBook addressBook3 = AddressBook.parseFrom(new FileInputStream("AddressBook1.txt")); + System.out.println("文件读取反序列化:"); + System.out.println(addressBook3); + } +} diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest3.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest3.java new file mode 100644 index 0000000..6f3857c --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest3.java @@ -0,0 +1,65 @@ +package com.wdbyte.tool.protos; + +import java.io.IOException; +import java.util.ArrayList; + +import com.alibaba.fastjson.JSON; + +import com.wdbyte.tool.protos.AddressBook.Builder; +import com.wdbyte.tool.protos.AddressBookJava.PersonJava; +import com.wdbyte.tool.protos.AddressBookJava.PhoneNumberJava; +import com.wdbyte.tool.protos.AddressBookJava.PhoneTypeJava; +import com.wdbyte.tool.protos.Person.PhoneNumber; +import com.wdbyte.tool.protos.Person.PhoneType; + +/** + * @author https://www.wdbyte.com + */ +public class ProtobufTest3 { + + public static void main(String[] args) throws IOException { + AddressBookJava addressBookJava = createAddressBookJava(1000); + String jsonString = JSON.toJSONString(addressBookJava); + System.out.println("json string size:" + jsonString.length()); + + AddressBook addressBook = createAddressBook(1000); + byte[] addressBookByteArray = addressBook.toByteArray(); + System.out.println("protobuf byte array size:" + addressBookByteArray.length); + } + + public static AddressBook createAddressBook(int personCount) { + Builder builder = AddressBook.newBuilder(); + for (int i = 0; i < personCount; i++) { + builder.addPeople(Person.newBuilder() + .setId(i) + .setName("www.wdbyte.com") + .setEmail("xxx@126.com") + .addPhones(PhoneNumber.newBuilder() + .setNumber("18333333333") + .setType(PhoneType.HOME) + ) + ); + } + return builder.build(); + } + + public static AddressBookJava createAddressBookJava(int personCount) { + AddressBookJava addressBookJava = new AddressBookJava(); + addressBookJava.setPersonJavaList(new ArrayList<>()); + for (int i = 0; i < personCount; i++) { + PersonJava personJava = new PersonJava(); + personJava.setId(i); + personJava.setName("www.wdbyte.com"); + personJava.setEmail("xxx@126.com"); + + PhoneNumberJava numberJava = new PhoneNumberJava(); + numberJava.setNumber("18333333333"); + numberJava.setPhoneTypeJava(PhoneTypeJava.HOME); + + personJava.setPhones(numberJava); + addressBookJava.getPersonJavaList().add(personJava); + } + return addressBookJava; + } +} + diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest4.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest4.java new file mode 100644 index 0000000..1437b42 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/ProtobufTest4.java @@ -0,0 +1,97 @@ +package com.wdbyte.tool.protos; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +import com.alibaba.fastjson.JSON; + +import com.google.protobuf.InvalidProtocolBufferException; +import com.wdbyte.tool.protos.AddressBook.Builder; +import com.wdbyte.tool.protos.AddressBookJava.PersonJava; +import com.wdbyte.tool.protos.AddressBookJava.PhoneNumberJava; +import com.wdbyte.tool.protos.AddressBookJava.PhoneTypeJava; +import com.wdbyte.tool.protos.Person.PhoneNumber; +import com.wdbyte.tool.protos.Person.PhoneType; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +/** + * @author https://www.wdbyte.com + */ +@State(Scope.Thread) +@Fork(2) +@Warmup(iterations = 3, time = 3) +@Measurement(iterations = 5, time = 3) +@BenchmarkMode(Mode.Throughput) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class ProtobufTest4 { + + private AddressBookJava addressBookJava; + private AddressBook addressBook; + + @Setup + public void init() { + addressBookJava = createAddressBookJava(1000); + addressBook = createAddressBook(1000); + } + + @Benchmark + public AddressBookJava testJSON() { + // 转 JSON + String jsonString = JSON.toJSONString(addressBookJava); + // JSON 转对象 + return JSON.parseObject(jsonString, AddressBookJava.class); + } + + @Benchmark + public AddressBook testProtobuf() throws InvalidProtocolBufferException { + // 转 JSON + byte[] addressBookByteArray = addressBook.toByteArray(); + // JSON 转对象 + return AddressBook.parseFrom(addressBookByteArray); + } + + public static AddressBook createAddressBook(int personCount) { + Builder builder = AddressBook.newBuilder(); + for (int i = 0; i < personCount; i++) { + builder.addPeople(Person.newBuilder() + .setId(i) + .setName("www.wdbyte.com") + .setEmail("xxx@126.com") + .addPhones(PhoneNumber.newBuilder() + .setNumber("18333333333") + .setType(PhoneType.HOME) + ) + ); + } + return builder.build(); + } + + public static AddressBookJava createAddressBookJava(int personCount) { + AddressBookJava addressBookJava = new AddressBookJava(); + addressBookJava.setPersonJavaList(new ArrayList<>()); + for (int i = 0; i < personCount; i++) { + PersonJava personJava = new PersonJava(); + personJava.setId(i); + personJava.setName("www.wdbyte.com"); + personJava.setEmail("xxx@126.com"); + + PhoneNumberJava numberJava = new PhoneNumberJava(); + numberJava.setNumber("18333333333"); + numberJava.setPhoneTypeJava(PhoneTypeJava.HOME); + + personJava.setPhones(numberJava); + addressBookJava.getPersonJavaList().add(personJava); + } + return addressBookJava; + } +} + diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentTest.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentTest.java new file mode 100644 index 0000000..041a689 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentTest.java @@ -0,0 +1,17 @@ +package com.wdbyte.tool.protos; + +import java.util.Arrays; + +import com.wdbyte.tool.protos.StudentOuterClass.Student; + +/** + * @author https://www.wdbyte.com + * @date 2023/05/10 + */ +public class StudentTest { + + public static void main(String[] args) { + Student student = Student.newBuilder().setId("1").setName("AB").build(); + System.out.println(Arrays.toString(student.toByteArray())); + } +} diff --git a/tool-java-protobuf/src/main/resources/addressbook.proto b/tool-java-protobuf/src/main/resources/addressbook.proto new file mode 100644 index 0000000..cbe93a9 --- /dev/null +++ b/tool-java-protobuf/src/main/resources/addressbook.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; +// 指定 protobuf 包名,防止有相同类名的 message 定义 +package com.wdbyte.protobuf; +// 是否生成多个文件 +option java_multiple_files = true; +// 生成的文件存放在哪个包下 +option java_package = "com.wdbyte.tool.protos"; +// 生成的类名,如果没有指定,会根据文件名自动转驼峰来命名 +option java_outer_classname = "AddressBookProtos"; +// 可选消息字段类型:bool int32 float double string +// 或者自定义消息类型,如下面的 PhoneNumber +// 修饰符:optional: 可选字段, +// 修饰符:repeated:可重复,如数组。 +// 修饰符:required:必要字段,必须给值,否则会报错 RuntimeException,但是在 protobuf 版本 3 中被移除。 +// 慎用 required,因为一旦被标记 requieds,以后将不能更改,否则可能会出问题。 +message Person { + optional int32 id = 1; + optional string name = 2; + optional string email = 3; + + enum PhoneType { + MOBILE = 0; + HOME = 1; + WORK = 2; + } + + message PhoneNumber { + optional string number = 1; + optional PhoneType type = 2; + } + + repeated PhoneNumber phones = 4; +} + +message AddressBook { + repeated Person people = 1; +} \ No newline at end of file diff --git a/tool-java-protobuf/src/main/resources/student.proto b/tool-java-protobuf/src/main/resources/student.proto new file mode 100644 index 0000000..d656f75 --- /dev/null +++ b/tool-java-protobuf/src/main/resources/student.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +// 指定 protobuf 包名,防止有相同类名的 message 定义 +package com.wdbyte.protobuf; +// 是否生成多个文件 +option java_multiple_files = false; +// 生成的文件存放在哪个包下 +option java_package = "com.wdbyte.tool.protos"; +// 生成的类名,如果没有指定,会根据文件名自动转驼峰来命名 +//option java_outer_classname = "Student"; + +message Student{ + optional string id = 1; + optional string name = 2; +} \ No newline at end of file From 5c996c689e0149a12b01b1057d67aa7498b385b0 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 15:58:17 +0800 Subject: [PATCH 060/105] feat: junit5 --- .../.gitignore | 0 .../README.md | 0 .../pom.xml | 2 +- .../src/main/java/com/wdbyte/test/junit5/Person.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitOrder.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitOther.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitParam.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/JunitAssert.java | 2 +- .../src/test/java/com/wdbyte/test/junit5/PersonTest.java | 2 +- 12 files changed, 10 insertions(+), 10 deletions(-) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/.gitignore (100%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/README.md (100%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/pom.xml (95%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/main/java/com/wdbyte/test/junit5/Person.java (80%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java (97%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java (96%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java (96%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitOther.java (96%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitParam.java (93%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java (94%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/JunitAssert.java (95%) rename {junit5-jupiter-starter => tool-java-junit5-jupiter-starter}/src/test/java/com/wdbyte/test/junit5/PersonTest.java (92%) diff --git a/junit5-jupiter-starter/.gitignore b/tool-java-junit5-jupiter-starter/.gitignore similarity index 100% rename from junit5-jupiter-starter/.gitignore rename to tool-java-junit5-jupiter-starter/.gitignore diff --git a/junit5-jupiter-starter/README.md b/tool-java-junit5-jupiter-starter/README.md similarity index 100% rename from junit5-jupiter-starter/README.md rename to tool-java-junit5-jupiter-starter/README.md diff --git a/junit5-jupiter-starter/pom.xml b/tool-java-junit5-jupiter-starter/pom.xml similarity index 95% rename from junit5-jupiter-starter/pom.xml rename to tool-java-junit5-jupiter-starter/pom.xml index d100632..554f3de 100644 --- a/junit5-jupiter-starter/pom.xml +++ b/tool-java-junit5-jupiter-starter/pom.xml @@ -10,7 +10,7 @@ 4.0.0 com.wdbyte.junit5 - junit5-jupiter-starter + tool-java-junit5-jupiter-starter 1.8 diff --git a/junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java b/tool-java-junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java similarity index 80% rename from junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java rename to tool-java-junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java index 514c0af..975d7c6 100644 --- a/junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java +++ b/tool-java-junit5-jupiter-starter/src/main/java/com/wdbyte/test/junit5/Person.java @@ -1,7 +1,7 @@ package com.wdbyte.test.junit5; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ public class Person { diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java similarity index 97% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java index 732fb50..8c28e80 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitBeforeAll.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/15 */ class JUnitBeforeAll { diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java similarity index 96% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java index 62230fc..d610a96 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitJDKVersion.java @@ -11,7 +11,7 @@ import static org.junit.jupiter.api.condition.JRE.JAVA_19; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ @TestMethodOrder(OrderAnnotation.class) diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java similarity index 96% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java index eef289a..6bd959e 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOrder.java @@ -8,7 +8,7 @@ import org.junit.jupiter.api.TestMethodOrder; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ @TestMethodOrder(OrderAnnotation.class) diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java similarity index 96% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java index aebe289..9951745 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitOther.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.TestMethodOrder; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ @TestMethodOrder(OrderAnnotation.class) diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java similarity index 93% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java index fe29bbd..388a865 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitParam.java @@ -6,7 +6,7 @@ import org.junit.jupiter.params.provider.ValueSource; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ public class JUnitParam { diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java similarity index 94% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java index 5d0cb07..e39c696 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JUnitTestIsDog.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/15 */ class JUnitTestIsDog { diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java similarity index 95% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java index 0063739..34a29e6 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/JunitAssert.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ public class JunitAssert { diff --git a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java similarity index 92% rename from junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java rename to tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java index 0d883ed..d72ef3d 100644 --- a/junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java +++ b/tool-java-junit5-jupiter-starter/src/test/java/com/wdbyte/test/junit5/PersonTest.java @@ -5,7 +5,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/17 */ @DisplayName("测试 Presion") From efd1d904510fb405041df9ece6fa4716f1ec7102 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 16:08:46 +0800 Subject: [PATCH 061/105] =?UTF-8?q?docs:=20=E4=BF=AE=E6=AD=A3=E6=B3=A8?= =?UTF-8?q?=E9=87=8A=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core-java-20/src/JEP433SwitchTest.java | 2 +- .../core-java-20/src/Jep432RecordAndInstance.java | 2 +- core-java-modules/core-java-20/src/RecordTest.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Base64.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Function.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Interface.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Lambda.java | 2 +- .../src/main/java/com/wdbyte/Jdk8LocalDateTime.java | 2 +- .../src/main/java/com/wdbyte/Jdk8NashornJs.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Optional.java | 2 +- .../src/main/java/com/wdbyte/Jdk8Stream.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator2.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator3.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator4.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator5.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator6.java | 2 +- .../java/com/wdbyte/comparator/Java8Comparator7.java | 2 +- .../src/main/java/com/wdbyte/JavaDataType.java | 2 +- .../src/main/java/com/wdbyte/array/JavaArray.java | 2 +- .../src/main/java/com/wdbyte/array/JavaArray2.java | 2 +- .../src/main/java/com/wdbyte/array/JavaArray3.java | 2 +- .../src/main/java/com/wdbyte/array/JavaArray4.java | 2 +- .../java/com/wdbyte/collection/CollectionTest.java | 2 +- .../java/com/wdbyte/collection/CollectionTest2.java | 2 +- .../java/com/wdbyte/collection/CollectionTest3.java | 2 +- .../java/com/wdbyte/collection/CollectionTest4.java | 2 +- .../java/com/wdbyte/collection/CollectionTest5.java | 2 +- .../src/main/java/com/wdbyte/collection/MapTest.java | 2 +- .../src/main/java/com/wdbyte/collection/MapTest2.java | 2 +- .../src/main/java/com/wdbyte/date/JavaDateCalc.java | 2 +- .../src/main/java/com/wdbyte/date/JavaDateCalc2.java | 2 +- .../src/main/java/com/wdbyte/date/JavaDateCreate.java | 2 +- .../src/main/java/com/wdbyte/date/JavaDateDiff.java | 2 +- .../src/main/java/com/wdbyte/date/JavaDateFormat.java | 2 +- .../src/main/java/com/wdbyte/enum2/CalcTest.java | 2 +- .../src/main/java/com/wdbyte/enum2/WeekdayTest.java | 2 +- .../main/java/com/wdbyte/exception/JavaException1.java | 2 +- .../main/java/com/wdbyte/exception/JavaException2.java | 2 +- .../main/java/com/wdbyte/exception/JavaException3.java | 2 +- .../main/java/com/wdbyte/exception/JavaException4.java | 2 +- .../main/java/com/wdbyte/exception/JavaException5.java | 2 +- .../main/java/com/wdbyte/exception/JavaException6.java | 2 +- .../main/java/com/wdbyte/exception/JavaException7.java | 2 +- .../main/java/com/wdbyte/exception/JavaException8.java | 2 +- .../src/main/java/com/wdbyte/oop/JavaExtends.java | 2 +- .../src/main/java/com/wdbyte/oop/abs/AbsPerson.java | 2 +- .../src/main/java/com/wdbyte/oop/abs/PersonTest.java | 2 +- .../src/main/java/com/wdbyte/oop/abs/Student.java | 2 +- .../src/main/java/com/wdbyte/oop/abs/Teacher.java | 2 +- .../java/com/wdbyte/oop/interfac/JavaInterface.java | 2 +- .../java/com/wdbyte/oop/interfac/JavaInterface2.java | 2 +- .../java/com/wdbyte/oop/interfac/JavaInterface3.java | 2 +- .../java/com/wdbyte/oop/polymorphism/AliyunOss.java | 2 +- .../src/main/java/com/wdbyte/oop/polymorphism/Oss.java | 2 +- .../main/java/com/wdbyte/oop/polymorphism/OssUtil.java | 2 +- .../java/com/wdbyte/oop/polymorphism/TencentOss.java | 2 +- .../java/com/wdbyte/oop/polymorphism/inter/Test.java | 2 +- .../java/com/wdbyte/oop/polymorphism/inter/Test2.java | 2 +- .../main/java/com/wdbyte/{ => string}/JavaString.java | 4 ++-- .../com/wdbyte/{ => string}/JavaStringBuilder.java | 4 ++-- .../src/main/java/com/wdbyte/HashMapKey.java | 10 +++++++++- 62 files changed, 72 insertions(+), 64 deletions(-) rename core-java-modules/core-java-base/src/main/java/com/wdbyte/{ => string}/JavaString.java (97%) rename core-java-modules/core-java-base/src/main/java/com/wdbyte/{ => string}/JavaStringBuilder.java (94%) diff --git a/core-java-modules/core-java-20/src/JEP433SwitchTest.java b/core-java-modules/core-java-20/src/JEP433SwitchTest.java index 8080d37..39094ad 100644 --- a/core-java-modules/core-java-20/src/JEP433SwitchTest.java +++ b/core-java-modules/core-java-20/src/JEP433SwitchTest.java @@ -1,5 +1,5 @@ /** - * @author niulang + * @author https://www.wdbyte.com */ public class JEP433SwitchTest { public static void main(String[] args) { diff --git a/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java b/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java index 3e732a6..6ca31d5 100644 --- a/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java +++ b/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java @@ -1,5 +1,5 @@ /** - * @author niulang + * @author https://www.wdbyte.com */ public class Jep432RecordAndInstance { public static void main(String[] args) { diff --git a/core-java-modules/core-java-20/src/RecordTest.java b/core-java-modules/core-java-20/src/RecordTest.java index ef9a7c1..2a3b46c 100644 --- a/core-java-modules/core-java-20/src/RecordTest.java +++ b/core-java-modules/core-java-20/src/RecordTest.java @@ -1,5 +1,5 @@ /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/05/04 */ public class RecordTest { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Base64.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Base64.java index f3034f9..c50f47c 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Base64.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Base64.java @@ -7,7 +7,7 @@ *

* JDK8 对 base64 编码的支持 * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/6/12 9:47 */ public class Jdk8Base64 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java index 78d045b..9d14a98 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Function.java @@ -14,7 +14,7 @@ * - Consumer: 数据消费器, 接收一个 T类型的对象,无返回值,通常用于设置T对象的值; 单参数无返回值的行为接口;提供了 accept, andThen 方法
* - Predicate: 条件测试器,接收一个 T 类型的对象,返回布尔值,通常用于传递条件函数; 单参数布尔值的条件性接口。提供了 test (条件测试) , and-or- negate(与或非) 方法。 * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/2/18 22:08 */ public class Jdk8Function { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Interface.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Interface.java index 4037c71..790262a 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Interface.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Interface.java @@ -5,7 +5,7 @@ * 接口的静态方法和默认方法 * * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/2/18 22:52 */ public class Jdk8Interface { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java index b2663cb..357f967 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java @@ -23,7 +23,7 @@ * 4. 花括号可选,一个语句可以不用花括号,多个参数则花括号必须。 * 5. 返回值可选,如果只有一个表达式,可以自动返回不需要 return 语句,花括号中需要 return 语法。 * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/2/17 14:48 */ public class Jdk8Lambda { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java index c41caf0..9af6004 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8LocalDateTime.java @@ -19,7 +19,7 @@ * - 实现了大部分常用操作方法 *

* - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/2/19 17:48 */ public class Jdk8LocalDateTime { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java index 6a1a2e7..ac34fd1 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8NashornJs.java @@ -15,7 +15,7 @@ * * 这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下: * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/6/12 9:41 */ public class Jdk8NashornJs { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java index 4288b27..ec3fa57 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java @@ -10,7 +10,7 @@ *

* JDK8 为解决空指针增加的 Optional 方法 * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/2/19 11:40 */ public class Jdk8Optional { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java index aa1a6b5..d78cdb2 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Stream.java @@ -13,7 +13,7 @@ *

* JDK 8 steam 流操作 * - * @Author niujinpeng +* @Author https://www.wdbyte.com * @Date 2019/8/12 18:03 */ public class Jdk8Stream { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java index 78f7109..b4c5773 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator.java @@ -6,7 +6,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java index bef75cc..b53e60e 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator2.java @@ -6,7 +6,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator2 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java index 70d3a24..4031315 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator3.java @@ -5,7 +5,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator3 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java index 250e30a..c02c30d 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator4.java @@ -5,7 +5,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator4 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java index d65054b..ca3ae94 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator5.java @@ -6,7 +6,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator5 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java index bec0fb7..20ab437 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator6.java @@ -6,7 +6,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator6 { diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java index c7c9d25..2918e31 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/comparator/Java8Comparator7.java @@ -5,7 +5,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/02 */ public class Java8Comparator7 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java index 24aa637..192314b 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaDataType.java @@ -1,7 +1,7 @@ package com.wdbyte; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/23 */ public class JavaDataType { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java index daac4ec..521b1c2 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray.java @@ -5,7 +5,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/23 */ public class JavaArray { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java index b5a5b66..ba92287 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray2.java @@ -1,7 +1,7 @@ package com.wdbyte.array; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/25 */ public class JavaArray2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java index d48cd29..3816c54 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray3.java @@ -1,7 +1,7 @@ package com.wdbyte.array; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/25 */ public class JavaArray3 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java index b1b998c..4665cc6 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/array/JavaArray4.java @@ -2,7 +2,7 @@ /** * 锯齿数组 - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/25 */ public class JavaArray4 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java index 67b150a..fc953ed 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com */ public class CollectionTest { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java index dfab350..3c67874 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest2.java @@ -9,7 +9,7 @@ import java.util.TreeSet; /** - * @author niulang + * @author https://www.wdbyte.com */ public class CollectionTest2 { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java index 7f75e07..2989ef5 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest3.java @@ -6,7 +6,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com */ public class CollectionTest3 { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java index 990fea2..349619b 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest4.java @@ -7,7 +7,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com */ public class CollectionTest4 { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java index 2dc01b0..6d480e8 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/CollectionTest5.java @@ -7,7 +7,7 @@ import java.util.List; /** - * @author niulang + * @author https://www.wdbyte.com */ public class CollectionTest5 { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java index b19d7b1..cd346f9 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest.java @@ -4,7 +4,7 @@ import java.util.Map; /** - * @author niulang + * @author https://www.wdbyte.com */ public class MapTest { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java index 306e313..235c79a 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/MapTest2.java @@ -5,7 +5,7 @@ import java.util.Map; /** - * @author niulang + * @author https://www.wdbyte.com */ public class MapTest2 { public static void main(String[] args) { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java index e400cdb..5c39fa8 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc.java @@ -5,7 +5,7 @@ import java.util.Date; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/26 */ public class JavaDateCalc { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java index 16f0c4a..922030f 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCalc2.java @@ -5,7 +5,7 @@ import java.util.Date; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/26 */ public class JavaDateCalc2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java index 2046ffe..44c6bbb 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateCreate.java @@ -3,7 +3,7 @@ import java.util.Date; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/25 */ public class JavaDateCreate { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java index 2f47ade..09e3363 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateDiff.java @@ -4,7 +4,7 @@ import java.util.Date; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/25 */ public class JavaDateDiff { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java index 47be812..96a3c89 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/date/JavaDateFormat.java @@ -5,7 +5,7 @@ import java.util.Date; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/25 */ public class JavaDateFormat { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java index b9eeff9..56e2465 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/CalcTest.java @@ -1,7 +1,7 @@ package com.wdbyte.enum2; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/05/01 */ public class CalcTest { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java index 0478277..6d83265 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java @@ -1,7 +1,7 @@ package com.wdbyte.enum2; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/05/01 */ public class WeekdayTest { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java index 18b22d1..8fe2506 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException1.java @@ -1,7 +1,7 @@ package com.wdbyte.exception; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException1 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java index e680d35..c5b2386 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException2.java @@ -1,7 +1,7 @@ package com.wdbyte.exception; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java index 4395064..5e37b10 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException3.java @@ -1,7 +1,7 @@ package com.wdbyte.exception; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException3 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java index a88f5e5..0ba0f3d 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException4.java @@ -1,7 +1,7 @@ package com.wdbyte.exception; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException4 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java index acbb901..9366d98 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException5.java @@ -3,7 +3,7 @@ import org.apache.commons.lang3.ObjectUtils.Null; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException5 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java index 4cbff20..2b68230 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException6.java @@ -5,7 +5,7 @@ import java.nio.file.Paths; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/27 */ public class JavaException6 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java index 9b5483d..bdbbc19 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException7.java @@ -5,7 +5,7 @@ import java.nio.file.Paths; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/27 */ public class JavaException7 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java index 905a2ab..1f86646 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/exception/JavaException8.java @@ -1,7 +1,7 @@ package com.wdbyte.exception; /** - * @author niulang + * @author https://www.wdbyte.com */ public class JavaException8 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java index 7811d7f..df818b3 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/JavaExtends.java @@ -1,7 +1,7 @@ package com.wdbyte.oop; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/31 */ public class JavaExtends { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java index 062bb70..c142711 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.abs; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/17 */ public abstract class AbsPerson { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java index 3c8d06e..02144e2 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/PersonTest.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.abs; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/17 */ public class PersonTest { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java index bd9060f..dbfa7b7 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Student.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.abs; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/17 */ public class Student extends AbsPerson { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java index 8c790c8..1db8158 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/Teacher.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.abs; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/17 */ public class Teacher extends AbsPerson { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java index 7359a11..e700d09 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.interfac; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/13 */ public class JavaInterface { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java index b4bce89..db299b1 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface2.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.interfac; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/13 */ public class JavaInterface2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java index d4a75a9..abd9ff9 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/JavaInterface3.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.interfac; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/14 */ public class JavaInterface3 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java index d6229c3..3bedb6c 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/AliyunOss.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.polymorphism; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public class AliyunOss implements Oss { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java index f7466eb..8fd2af8 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/Oss.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.polymorphism; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public interface Oss { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java index 1e9eeef..5a07fc1 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/OssUtil.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.polymorphism; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public class OssUtil { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java index ece1f22..4993abd 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/TencentOss.java @@ -1,7 +1,7 @@ package com.wdbyte.oop.polymorphism; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public class TencentOss implements Oss { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java index 7565cd6..9d15814 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test.java @@ -4,7 +4,7 @@ /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public class Test { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java index 778c203..a1b7e3b 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/polymorphism/inter/Test2.java @@ -2,7 +2,7 @@ /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/04/18 */ public class Test2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaString.java similarity index 97% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaString.java index 31890ee..bc4d4e6 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaString.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaString.java @@ -1,7 +1,7 @@ -package com.wdbyte; +package com.wdbyte.string; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/22 */ public class JavaString { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaStringBuilder.java similarity index 94% rename from core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java rename to core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaStringBuilder.java index b492031..514054b 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/JavaStringBuilder.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/string/JavaStringBuilder.java @@ -1,7 +1,7 @@ -package com.wdbyte; +package com.wdbyte.string; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/03/30 */ public class JavaStringBuilder { diff --git a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java index 1fc032e..31e622b 100644 --- a/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java +++ b/core-java-modules/core-java-performance-code/src/main/java/com/wdbyte/HashMapKey.java @@ -3,13 +3,18 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.TimeUnit; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Level; import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.OperationsPerInvocation; +import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; @@ -20,9 +25,12 @@ * @author https://www.wdbyte.com * @date 2021/12/06 */ -@State(Scope.Benchmark) +@State(Scope.Thread) +@Fork(2) @Warmup(iterations = 3, time = 3) @Measurement(iterations = 5, time = 3) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) public class HashMapKey { private int size = 1024; From da5c648fcee5c8bd065584a7f845222e61dd9304 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 16:11:07 +0800 Subject: [PATCH 062/105] update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9e77cfc..2af0b44 100644 --- a/README.md +++ b/README.md @@ -184,6 +184,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 >出处:孔子《论语》 一款好用的工具,不仅可以装X,更可以让你事半功倍,准时下班。 +- [Protobuf 教程](https://www.wdbyte.com/tool/protobuf/) - [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html) - [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) - [Java 反编译工具的使用与对比分析](https://www.wdbyte.com/2021/05/java-decompiler/) From bdc331c1ac2ae2f0f22e9fdbd2b1ec2714a1f602 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 16:11:33 +0800 Subject: [PATCH 063/105] feat: leetcode --- .../LeetCode388_LengthLongestPath.java | 2 +- .../LeetCode396_MaxRotateFunction.java | 2 +- ...etCode515_FindLargeValueInEachTreeRow.java | 2 +- .../leetcode/LeetCode587_OuterTrees.java | 48 +++++++++++++++++++ .../leetcode/LeetCode824_ToGoatLatin.java | 2 +- 5 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 leetcode/src/main/java/com/wdbyte/leetcode/LeetCode587_OuterTrees.java diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java index 8a41422..46a0ea9 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode388_LengthLongestPath.java @@ -6,7 +6,7 @@ * 388. 文件的最长绝对路径 * https://leetcode-cn.com/problems/longest-absolute-file-path/ * - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/20 */ public class LeetCode388_LengthLongestPath { diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java index adbe6e1..2e563ef 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode396_MaxRotateFunction.java @@ -22,7 +22,7 @@ * F(3) = (0 * 3) + (1 * 2) + (2 * 6) + (3 * 4) = 0 + 2 + 12 + 12 = 26 * 所以 F(0), F(1), F(2), F(3) 中的最大值是 F(3) = 26 。 * - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/22 */ public class LeetCode396_MaxRotateFunction { diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java index 141a534..3560d97 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode515_FindLargeValueInEachTreeRow.java @@ -8,7 +8,7 @@ * * 515. 在每个树行中找最大值 * - * @author niulang + * @author https://www.wdbyte.com * @date 2022/06/24 */ public class LeetCode515_FindLargeValueInEachTreeRow { diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode587_OuterTrees.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode587_OuterTrees.java new file mode 100644 index 0000000..ed2a356 --- /dev/null +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode587_OuterTrees.java @@ -0,0 +1,48 @@ +package com.wdbyte.leetcode; + +/** + * https://leetcode-cn.com/problems/erect-the-fence/ + * 587. 安装栅栏 + * 在一个二维的花园中,有一些用 (x, y) 坐标表示的树。由于安装费用十分昂贵,你的任务是先用最短的绳子围起 + * 所有的树。只有当所有的树都被绳子包围时,花园才能围好栅栏。你需要找到正好位于栅栏边界上的树的坐标。 + * + * @author https://www.wdbyte.com + * @date 2022/04/23 + */ +public class LeetCode587_OuterTrees { + public static void main(String[] args) { + int[][] trees = {{1, 1}, {2, 2}, {2, 0}, {2, 4}, {3, 3}, {4, 2}}; + System.out.println(multi(trees[2], trees[2], trees[4])); + } + + public int[][] outerTrees(int[][] trees) { + // 1. 找到最左边的一个点 + int startX = 0; + int startY = 0; + for (int[] tree : trees) { + int x = tree[0]; + int y = tree[1]; + if (x < startX) { + startX = x; + startY = y; + } + } + // 2. 从最左边的一个点开始,寻找最大角度的点的连线 + return null; + } + + static int[][] sort(int[][] trees) { + return trees; + } + + //计算叉积,p1,p2,p0都为点 + static double multi(int[] p1, int[] p2, int[] p0) { + double x1, y1, x2, y2; + x1 = p1[0] - p0[0]; + y1 = p1[1] - p0[1]; + x2 = p2[0] - p0[0]; + y2 = p2[1] - p0[1]; + return x1 * y2 - x2 * y1; + } + +} diff --git a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java index 43a4c12..dbe5e05 100644 --- a/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java +++ b/leetcode/src/main/java/com/wdbyte/leetcode/LeetCode824_ToGoatLatin.java @@ -4,7 +4,7 @@ * https://leetcode-cn.com/problems/goat-latin/ * 824. 山羊拉丁文 * - * @author niulang + * @author https://www.wdbyte.com * @date 2022/04/21 */ public class LeetCode824_ToGoatLatin { From ddce4729b86b5257a620496a55b0bb610d70f8b9 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 16:11:56 +0800 Subject: [PATCH 064/105] update HotCode.java --- .../main/java/com/wdbyte/hotcode/HotCode.java | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java index 0750444..0838ffb 100644 --- a/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java +++ b/tool-java-hotcode/src/main/java/com/wdbyte/hotcode/HotCode.java @@ -1,6 +1,9 @@ package com.wdbyte.hotcode; +import java.io.IOException; import java.math.BigDecimal; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -9,7 +12,7 @@ /** * - * @author niulang + * @author https://www.wdbyte.com * @date 2023/02/20 */ public class HotCode { @@ -33,6 +36,10 @@ public static void main(String[] args) { thread(); // 运行缓慢的方法 runSlowThread(); + // 读取文件 + readFile(); + // 抛出异常 + exceMethod(); } /** @@ -104,6 +111,7 @@ private static void deadThread() { System.out.println(Thread.currentThread() + " get ResourceB"); try { Thread.sleep(1000); + } catch (InterruptedException e) { e.printStackTrace(); } @@ -194,4 +202,45 @@ public static void slow2() throws InterruptedException { System.out.println(count); } + /** + * 不断读取文件 + */ + public static void readFile(){ + new Thread(() -> { + Thread.currentThread().setName("read_file_method"); + while (true){ + try { + byte[] bytes = Files.readAllBytes(Paths.get("/Users/darcy/Downloads/info.txt")); + System.out.println(bytes.length); + Thread.sleep(100); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }).start(); + } + + /** + * 不断抛出异常 + */ + public static void exceMethod() { + new Thread(() -> { + Thread.currentThread().setName("exce_method"); + while (true) { + try { + System.out.println(exce(0)); + Thread.sleep(200); + } catch (Exception e) { + e.printStackTrace(); + } + } + }).start(); + } + + public static int exce(int a){ + return 10/a; + } + } From 9717e5a6c7c0927b5e6c17f0921670753550ab0d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Tue, 16 May 2023 16:25:04 +0800 Subject: [PATCH 065/105] feat: protobuf code --- tool-java-protobuf/pom.xml | 1 + .../com/wdbyte/tool/protos/AddressBook.java | 725 ++++++ .../tool/protos/AddressBookOrBuilder.java | 33 + .../wdbyte/tool/protos/AddressBookProtos.java | 79 + .../java/com/wdbyte/tool/protos/Person.java | 1942 +++++++++++++++++ .../wdbyte/tool/protos/PersonOrBuilder.java | 78 + .../wdbyte/tool/protos/StudentOuterClass.java | 770 +++++++ 7 files changed, 3628 insertions(+) create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBook.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookOrBuilder.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookProtos.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/Person.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/PersonOrBuilder.java create mode 100644 tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentOuterClass.java diff --git a/tool-java-protobuf/pom.xml b/tool-java-protobuf/pom.xml index 36b0b7f..3c9725c 100644 --- a/tool-java-protobuf/pom.xml +++ b/tool-java-protobuf/pom.xml @@ -7,6 +7,7 @@ com.wdbyte parent-modules 1.0.0-SNAPSHOT + ../pom.xml tool-java-protobuf diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBook.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBook.java new file mode 100644 index 0000000..e8e78be --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBook.java @@ -0,0 +1,725 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/addressbook.proto + +package com.wdbyte.tool.protos; + +/** + * Protobuf type {@code com.wdbyte.protobuf.AddressBook} + */ +public final class AddressBook extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.wdbyte.protobuf.AddressBook) + AddressBookOrBuilder { +private static final long serialVersionUID = 0L; + // Use AddressBook.newBuilder() to construct. + private AddressBook(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private AddressBook() { + people_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new AddressBook(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_AddressBook_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_AddressBook_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.AddressBook.class, com.wdbyte.tool.protos.AddressBook.Builder.class); + } + + public static final int PEOPLE_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private java.util.List people_; + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + @java.lang.Override + public java.util.List getPeopleList() { + return people_; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + @java.lang.Override + public java.util.List + getPeopleOrBuilderList() { + return people_; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + @java.lang.Override + public int getPeopleCount() { + return people_.size(); + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + @java.lang.Override + public com.wdbyte.tool.protos.Person getPeople(int index) { + return people_.get(index); + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + @java.lang.Override + public com.wdbyte.tool.protos.PersonOrBuilder getPeopleOrBuilder( + int index) { + return people_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + for (int i = 0; i < people_.size(); i++) { + output.writeMessage(1, people_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + for (int i = 0; i < people_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(1, people_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.wdbyte.tool.protos.AddressBook)) { + return super.equals(obj); + } + com.wdbyte.tool.protos.AddressBook other = (com.wdbyte.tool.protos.AddressBook) obj; + + if (!getPeopleList() + .equals(other.getPeopleList())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (getPeopleCount() > 0) { + hash = (37 * hash) + PEOPLE_FIELD_NUMBER; + hash = (53 * hash) + getPeopleList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.wdbyte.tool.protos.AddressBook parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.AddressBook parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.AddressBook parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.AddressBook parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.wdbyte.tool.protos.AddressBook prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code com.wdbyte.protobuf.AddressBook} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:com.wdbyte.protobuf.AddressBook) + com.wdbyte.tool.protos.AddressBookOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_AddressBook_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_AddressBook_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.AddressBook.class, com.wdbyte.tool.protos.AddressBook.Builder.class); + } + + // Construct using com.wdbyte.tool.protos.AddressBook.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + if (peopleBuilder_ == null) { + people_ = java.util.Collections.emptyList(); + } else { + people_ = null; + peopleBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_AddressBook_descriptor; + } + + @java.lang.Override + public com.wdbyte.tool.protos.AddressBook getDefaultInstanceForType() { + return com.wdbyte.tool.protos.AddressBook.getDefaultInstance(); + } + + @java.lang.Override + public com.wdbyte.tool.protos.AddressBook build() { + com.wdbyte.tool.protos.AddressBook result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.wdbyte.tool.protos.AddressBook buildPartial() { + com.wdbyte.tool.protos.AddressBook result = new com.wdbyte.tool.protos.AddressBook(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(com.wdbyte.tool.protos.AddressBook result) { + if (peopleBuilder_ == null) { + if (((bitField0_ & 0x00000001) != 0)) { + people_ = java.util.Collections.unmodifiableList(people_); + bitField0_ = (bitField0_ & ~0x00000001); + } + result.people_ = people_; + } else { + result.people_ = peopleBuilder_.build(); + } + } + + private void buildPartial0(com.wdbyte.tool.protos.AddressBook result) { + int from_bitField0_ = bitField0_; + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.wdbyte.tool.protos.AddressBook) { + return mergeFrom((com.wdbyte.tool.protos.AddressBook)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.wdbyte.tool.protos.AddressBook other) { + if (other == com.wdbyte.tool.protos.AddressBook.getDefaultInstance()) return this; + if (peopleBuilder_ == null) { + if (!other.people_.isEmpty()) { + if (people_.isEmpty()) { + people_ = other.people_; + bitField0_ = (bitField0_ & ~0x00000001); + } else { + ensurePeopleIsMutable(); + people_.addAll(other.people_); + } + onChanged(); + } + } else { + if (!other.people_.isEmpty()) { + if (peopleBuilder_.isEmpty()) { + peopleBuilder_.dispose(); + peopleBuilder_ = null; + people_ = other.people_; + bitField0_ = (bitField0_ & ~0x00000001); + peopleBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getPeopleFieldBuilder() : null; + } else { + peopleBuilder_.addAllMessages(other.people_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + com.wdbyte.tool.protos.Person m = + input.readMessage( + com.wdbyte.tool.protos.Person.parser(), + extensionRegistry); + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + people_.add(m); + } else { + peopleBuilder_.addMessage(m); + } + break; + } // case 10 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.util.List people_ = + java.util.Collections.emptyList(); + private void ensurePeopleIsMutable() { + if (!((bitField0_ & 0x00000001) != 0)) { + people_ = new java.util.ArrayList(people_); + bitField0_ |= 0x00000001; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person, com.wdbyte.tool.protos.Person.Builder, com.wdbyte.tool.protos.PersonOrBuilder> peopleBuilder_; + + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public java.util.List getPeopleList() { + if (peopleBuilder_ == null) { + return java.util.Collections.unmodifiableList(people_); + } else { + return peopleBuilder_.getMessageList(); + } + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public int getPeopleCount() { + if (peopleBuilder_ == null) { + return people_.size(); + } else { + return peopleBuilder_.getCount(); + } + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public com.wdbyte.tool.protos.Person getPeople(int index) { + if (peopleBuilder_ == null) { + return people_.get(index); + } else { + return peopleBuilder_.getMessage(index); + } + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder setPeople( + int index, com.wdbyte.tool.protos.Person value) { + if (peopleBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePeopleIsMutable(); + people_.set(index, value); + onChanged(); + } else { + peopleBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder setPeople( + int index, com.wdbyte.tool.protos.Person.Builder builderForValue) { + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + people_.set(index, builderForValue.build()); + onChanged(); + } else { + peopleBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder addPeople(com.wdbyte.tool.protos.Person value) { + if (peopleBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePeopleIsMutable(); + people_.add(value); + onChanged(); + } else { + peopleBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder addPeople( + int index, com.wdbyte.tool.protos.Person value) { + if (peopleBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePeopleIsMutable(); + people_.add(index, value); + onChanged(); + } else { + peopleBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder addPeople( + com.wdbyte.tool.protos.Person.Builder builderForValue) { + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + people_.add(builderForValue.build()); + onChanged(); + } else { + peopleBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder addPeople( + int index, com.wdbyte.tool.protos.Person.Builder builderForValue) { + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + people_.add(index, builderForValue.build()); + onChanged(); + } else { + peopleBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder addAllPeople( + java.lang.Iterable values) { + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, people_); + onChanged(); + } else { + peopleBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder clearPeople() { + if (peopleBuilder_ == null) { + people_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + } else { + peopleBuilder_.clear(); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public Builder removePeople(int index) { + if (peopleBuilder_ == null) { + ensurePeopleIsMutable(); + people_.remove(index); + onChanged(); + } else { + peopleBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public com.wdbyte.tool.protos.Person.Builder getPeopleBuilder( + int index) { + return getPeopleFieldBuilder().getBuilder(index); + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public com.wdbyte.tool.protos.PersonOrBuilder getPeopleOrBuilder( + int index) { + if (peopleBuilder_ == null) { + return people_.get(index); } else { + return peopleBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public java.util.List + getPeopleOrBuilderList() { + if (peopleBuilder_ != null) { + return peopleBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(people_); + } + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public com.wdbyte.tool.protos.Person.Builder addPeopleBuilder() { + return getPeopleFieldBuilder().addBuilder( + com.wdbyte.tool.protos.Person.getDefaultInstance()); + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public com.wdbyte.tool.protos.Person.Builder addPeopleBuilder( + int index) { + return getPeopleFieldBuilder().addBuilder( + index, com.wdbyte.tool.protos.Person.getDefaultInstance()); + } + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + public java.util.List + getPeopleBuilderList() { + return getPeopleFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person, com.wdbyte.tool.protos.Person.Builder, com.wdbyte.tool.protos.PersonOrBuilder> + getPeopleFieldBuilder() { + if (peopleBuilder_ == null) { + peopleBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person, com.wdbyte.tool.protos.Person.Builder, com.wdbyte.tool.protos.PersonOrBuilder>( + people_, + ((bitField0_ & 0x00000001) != 0), + getParentForChildren(), + isClean()); + people_ = null; + } + return peopleBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.wdbyte.protobuf.AddressBook) + } + + // @@protoc_insertion_point(class_scope:com.wdbyte.protobuf.AddressBook) + private static final com.wdbyte.tool.protos.AddressBook DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.wdbyte.tool.protos.AddressBook(); + } + + public static com.wdbyte.tool.protos.AddressBook getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public AddressBook parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.wdbyte.tool.protos.AddressBook getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookOrBuilder.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookOrBuilder.java new file mode 100644 index 0000000..e0b5161 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookOrBuilder.java @@ -0,0 +1,33 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/addressbook.proto + +package com.wdbyte.tool.protos; + +public interface AddressBookOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.wdbyte.protobuf.AddressBook) + com.google.protobuf.MessageOrBuilder { + + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + java.util.List + getPeopleList(); + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + com.wdbyte.tool.protos.Person getPeople(int index); + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + int getPeopleCount(); + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + java.util.List + getPeopleOrBuilderList(); + /** + * repeated .com.wdbyte.protobuf.Person people = 1; + */ + com.wdbyte.tool.protos.PersonOrBuilder getPeopleOrBuilder( + int index); +} diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookProtos.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookProtos.java new file mode 100644 index 0000000..e1c6788 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/AddressBookProtos.java @@ -0,0 +1,79 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/addressbook.proto + +package com.wdbyte.tool.protos; + +public final class AddressBookProtos { + private AddressBookProtos() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_wdbyte_protobuf_Person_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_wdbyte_protobuf_Person_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_wdbyte_protobuf_Person_PhoneNumber_fieldAccessorTable; + static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_wdbyte_protobuf_AddressBook_descriptor; + static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_wdbyte_protobuf_AddressBook_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\033resources/addressbook.proto\022\023com.wdbyt" + + "e.protobuf\"\262\002\n\006Person\022\017\n\002id\030\001 \001(\005H\000\210\001\001\022\021" + + "\n\004name\030\002 \001(\tH\001\210\001\001\022\022\n\005email\030\003 \001(\tH\002\210\001\001\0227\n" + + "\006phones\030\004 \003(\0132\'.com.wdbyte.protobuf.Pers" + + "on.PhoneNumber\032p\n\013PhoneNumber\022\023\n\006number\030" + + "\001 \001(\tH\000\210\001\001\0228\n\004type\030\002 \001(\0162%.com.wdbyte.pr" + + "otobuf.Person.PhoneTypeH\001\210\001\001B\t\n\007_numberB" + + "\007\n\005_type\"+\n\tPhoneType\022\n\n\006MOBILE\020\000\022\010\n\004HOM" + + "E\020\001\022\010\n\004WORK\020\002B\005\n\003_idB\007\n\005_nameB\010\n\006_email\"" + + ":\n\013AddressBook\022+\n\006people\030\001 \003(\0132\033.com.wdb" + + "yte.protobuf.PersonB-\n\026com.wdbyte.tool.p" + + "rotosB\021AddressBookProtosP\001b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_com_wdbyte_protobuf_Person_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_com_wdbyte_protobuf_Person_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_wdbyte_protobuf_Person_descriptor, + new java.lang.String[] { "Id", "Name", "Email", "Phones", "Id", "Name", "Email", }); + internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor = + internal_static_com_wdbyte_protobuf_Person_descriptor.getNestedTypes().get(0); + internal_static_com_wdbyte_protobuf_Person_PhoneNumber_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor, + new java.lang.String[] { "Number", "Type", "Number", "Type", }); + internal_static_com_wdbyte_protobuf_AddressBook_descriptor = + getDescriptor().getMessageTypes().get(1); + internal_static_com_wdbyte_protobuf_AddressBook_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_wdbyte_protobuf_AddressBook_descriptor, + new java.lang.String[] { "People", }); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/Person.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/Person.java new file mode 100644 index 0000000..8ba2d40 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/Person.java @@ -0,0 +1,1942 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/addressbook.proto + +package com.wdbyte.tool.protos; + +/** + *

+ * 可选消息字段类型:bool int32 float double string
+ * 或者自定义消息类型,如下面的 PhoneNumber
+ * 修饰符:optional: 可选字段,
+ * 修饰符:repeated:可重复,如数组。
+ * 修饰符:required:必要字段,必须给值,否则会报错 RuntimeException,但是在 protobuf 版本 3 中被移除。
+ * 慎用 required,因为一旦被标记 requieds,以后将不能更改,否则可能会出问题。
+ * 
+ * + * Protobuf type {@code com.wdbyte.protobuf.Person} + */ +public final class Person extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.wdbyte.protobuf.Person) + PersonOrBuilder { +private static final long serialVersionUID = 0L; + // Use Person.newBuilder() to construct. + private Person(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Person() { + name_ = ""; + email_ = ""; + phones_ = java.util.Collections.emptyList(); + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Person(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.Person.class, com.wdbyte.tool.protos.Person.Builder.class); + } + + /** + * Protobuf enum {@code com.wdbyte.protobuf.Person.PhoneType} + */ + public enum PhoneType + implements com.google.protobuf.ProtocolMessageEnum { + /** + * MOBILE = 0; + */ + MOBILE(0), + /** + * HOME = 1; + */ + HOME(1), + /** + * WORK = 2; + */ + WORK(2), + UNRECOGNIZED(-1), + ; + + /** + * MOBILE = 0; + */ + public static final int MOBILE_VALUE = 0; + /** + * HOME = 1; + */ + public static final int HOME_VALUE = 1; + /** + * WORK = 2; + */ + public static final int WORK_VALUE = 2; + + + public final int getNumber() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalArgumentException( + "Can't get the number of an unknown enum value."); + } + return value; + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + * @deprecated Use {@link #forNumber(int)} instead. + */ + @java.lang.Deprecated + public static PhoneType valueOf(int value) { + return forNumber(value); + } + + /** + * @param value The numeric wire value of the corresponding enum entry. + * @return The enum associated with the given numeric wire value. + */ + public static PhoneType forNumber(int value) { + switch (value) { + case 0: return MOBILE; + case 1: return HOME; + case 2: return WORK; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static final com.google.protobuf.Internal.EnumLiteMap< + PhoneType> internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public PhoneType findValueByNumber(int number) { + return PhoneType.forNumber(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + if (this == UNRECOGNIZED) { + throw new java.lang.IllegalStateException( + "Can't get the descriptor of an unrecognized enum value."); + } + return getDescriptor().getValues().get(ordinal()); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return com.wdbyte.tool.protos.Person.getDescriptor().getEnumTypes().get(0); + } + + private static final PhoneType[] VALUES = values(); + + public static PhoneType valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + if (desc.getIndex() == -1) { + return UNRECOGNIZED; + } + return VALUES[desc.getIndex()]; + } + + private final int value; + + private PhoneType(int value) { + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:com.wdbyte.protobuf.Person.PhoneType) + } + + public interface PhoneNumberOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.wdbyte.protobuf.Person.PhoneNumber) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string number = 1; + * @return Whether the number field is set. + */ + boolean hasNumber(); + /** + * optional string number = 1; + * @return The number. + */ + java.lang.String getNumber(); + /** + * optional string number = 1; + * @return The bytes for number. + */ + com.google.protobuf.ByteString + getNumberBytes(); + + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return Whether the type field is set. + */ + boolean hasType(); + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The enum numeric value on the wire for type. + */ + int getTypeValue(); + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The type. + */ + com.wdbyte.tool.protos.Person.PhoneType getType(); + } + /** + * Protobuf type {@code com.wdbyte.protobuf.Person.PhoneNumber} + */ + public static final class PhoneNumber extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.wdbyte.protobuf.Person.PhoneNumber) + PhoneNumberOrBuilder { + private static final long serialVersionUID = 0L; + // Use PhoneNumber.newBuilder() to construct. + private PhoneNumber(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private PhoneNumber() { + number_ = ""; + type_ = 0; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new PhoneNumber(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_PhoneNumber_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.Person.PhoneNumber.class, com.wdbyte.tool.protos.Person.PhoneNumber.Builder.class); + } + + private int bitField0_; + public static final int NUMBER_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object number_ = ""; + /** + * optional string number = 1; + * @return Whether the number field is set. + */ + @java.lang.Override + public boolean hasNumber() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string number = 1; + * @return The number. + */ + @java.lang.Override + public java.lang.String getNumber() { + java.lang.Object ref = number_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + number_ = s; + return s; + } + } + /** + * optional string number = 1; + * @return The bytes for number. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNumberBytes() { + java.lang.Object ref = number_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + number_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int TYPE_FIELD_NUMBER = 2; + private int type_ = 0; + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return Whether the type field is set. + */ + @java.lang.Override public boolean hasType() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The enum numeric value on the wire for type. + */ + @java.lang.Override public int getTypeValue() { + return type_; + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The type. + */ + @java.lang.Override public com.wdbyte.tool.protos.Person.PhoneType getType() { + com.wdbyte.tool.protos.Person.PhoneType result = com.wdbyte.tool.protos.Person.PhoneType.forNumber(type_); + return result == null ? com.wdbyte.tool.protos.Person.PhoneType.UNRECOGNIZED : result; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, number_); + } + if (((bitField0_ & 0x00000002) != 0)) { + output.writeEnum(2, type_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, number_); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(2, type_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.wdbyte.tool.protos.Person.PhoneNumber)) { + return super.equals(obj); + } + com.wdbyte.tool.protos.Person.PhoneNumber other = (com.wdbyte.tool.protos.Person.PhoneNumber) obj; + + if (hasNumber() != other.hasNumber()) return false; + if (hasNumber()) { + if (!getNumber() + .equals(other.getNumber())) return false; + } + if (hasType() != other.hasType()) return false; + if (hasType()) { + if (type_ != other.type_) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasNumber()) { + hash = (37 * hash) + NUMBER_FIELD_NUMBER; + hash = (53 * hash) + getNumber().hashCode(); + } + if (hasType()) { + hash = (37 * hash) + TYPE_FIELD_NUMBER; + hash = (53 * hash) + type_; + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person.PhoneNumber parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.wdbyte.tool.protos.Person.PhoneNumber prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code com.wdbyte.protobuf.Person.PhoneNumber} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:com.wdbyte.protobuf.Person.PhoneNumber) + com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_PhoneNumber_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.Person.PhoneNumber.class, com.wdbyte.tool.protos.Person.PhoneNumber.Builder.class); + } + + // Construct using com.wdbyte.tool.protos.Person.PhoneNumber.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + number_ = ""; + type_ = 0; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_PhoneNumber_descriptor; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumber getDefaultInstanceForType() { + return com.wdbyte.tool.protos.Person.PhoneNumber.getDefaultInstance(); + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumber build() { + com.wdbyte.tool.protos.Person.PhoneNumber result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumber buildPartial() { + com.wdbyte.tool.protos.Person.PhoneNumber result = new com.wdbyte.tool.protos.Person.PhoneNumber(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(com.wdbyte.tool.protos.Person.PhoneNumber result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.number_ = number_; + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.type_ = type_; + to_bitField0_ |= 0x00000002; + } + result.bitField0_ |= to_bitField0_; + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.wdbyte.tool.protos.Person.PhoneNumber) { + return mergeFrom((com.wdbyte.tool.protos.Person.PhoneNumber)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.wdbyte.tool.protos.Person.PhoneNumber other) { + if (other == com.wdbyte.tool.protos.Person.PhoneNumber.getDefaultInstance()) return this; + if (other.hasNumber()) { + number_ = other.number_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (other.hasType()) { + setType(other.getType()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + number_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 16: { + type_ = input.readEnum(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object number_ = ""; + /** + * optional string number = 1; + * @return Whether the number field is set. + */ + public boolean hasNumber() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string number = 1; + * @return The number. + */ + public java.lang.String getNumber() { + java.lang.Object ref = number_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + number_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string number = 1; + * @return The bytes for number. + */ + public com.google.protobuf.ByteString + getNumberBytes() { + java.lang.Object ref = number_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + number_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string number = 1; + * @param value The number to set. + * @return This builder for chaining. + */ + public Builder setNumber( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + number_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * optional string number = 1; + * @return This builder for chaining. + */ + public Builder clearNumber() { + number_ = getDefaultInstance().getNumber(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * optional string number = 1; + * @param value The bytes for number to set. + * @return This builder for chaining. + */ + public Builder setNumberBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + number_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private int type_ = 0; + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return Whether the type field is set. + */ + @java.lang.Override public boolean hasType() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The enum numeric value on the wire for type. + */ + @java.lang.Override public int getTypeValue() { + return type_; + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @param value The enum numeric value on the wire for type to set. + * @return This builder for chaining. + */ + public Builder setTypeValue(int value) { + type_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return The type. + */ + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneType getType() { + com.wdbyte.tool.protos.Person.PhoneType result = com.wdbyte.tool.protos.Person.PhoneType.forNumber(type_); + return result == null ? com.wdbyte.tool.protos.Person.PhoneType.UNRECOGNIZED : result; + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @param value The type to set. + * @return This builder for chaining. + */ + public Builder setType(com.wdbyte.tool.protos.Person.PhoneType value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + type_ = value.getNumber(); + onChanged(); + return this; + } + /** + * optional .com.wdbyte.protobuf.Person.PhoneType type = 2; + * @return This builder for chaining. + */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000002); + type_ = 0; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.wdbyte.protobuf.Person.PhoneNumber) + } + + // @@protoc_insertion_point(class_scope:com.wdbyte.protobuf.Person.PhoneNumber) + private static final com.wdbyte.tool.protos.Person.PhoneNumber DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.wdbyte.tool.protos.Person.PhoneNumber(); + } + + public static com.wdbyte.tool.protos.Person.PhoneNumber getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public PhoneNumber parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumber getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private int bitField0_; + public static final int ID_FIELD_NUMBER = 1; + private int id_ = 0; + /** + * optional int32 id = 1; + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional int32 id = 1; + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + + public static final int NAME_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object name_ = ""; + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + @java.lang.Override + public boolean hasName() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string name = 2; + * @return The name. + */ + @java.lang.Override + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * optional string name = 2; + * @return The bytes for name. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int EMAIL_FIELD_NUMBER = 3; + @SuppressWarnings("serial") + private volatile java.lang.Object email_ = ""; + /** + * optional string email = 3; + * @return Whether the email field is set. + */ + @java.lang.Override + public boolean hasEmail() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional string email = 3; + * @return The email. + */ + @java.lang.Override + public java.lang.String getEmail() { + java.lang.Object ref = email_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + email_ = s; + return s; + } + } + /** + * optional string email = 3; + * @return The bytes for email. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getEmailBytes() { + java.lang.Object ref = email_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + email_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int PHONES_FIELD_NUMBER = 4; + @SuppressWarnings("serial") + private java.util.List phones_; + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + @java.lang.Override + public java.util.List getPhonesList() { + return phones_; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + @java.lang.Override + public java.util.List + getPhonesOrBuilderList() { + return phones_; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + @java.lang.Override + public int getPhonesCount() { + return phones_.size(); + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumber getPhones(int index) { + return phones_.get(index); + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + @java.lang.Override + public com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( + int index) { + return phones_.get(index); + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + output.writeInt32(1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, name_); + } + if (((bitField0_ & 0x00000004) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 3, email_); + } + for (int i = 0; i < phones_.size(); i++) { + output.writeMessage(4, phones_.get(i)); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, name_); + } + if (((bitField0_ & 0x00000004) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(3, email_); + } + for (int i = 0; i < phones_.size(); i++) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(4, phones_.get(i)); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.wdbyte.tool.protos.Person)) { + return super.equals(obj); + } + com.wdbyte.tool.protos.Person other = (com.wdbyte.tool.protos.Person) obj; + + if (hasId() != other.hasId()) return false; + if (hasId()) { + if (getId() + != other.getId()) return false; + } + if (hasName() != other.hasName()) return false; + if (hasName()) { + if (!getName() + .equals(other.getName())) return false; + } + if (hasEmail() != other.hasEmail()) return false; + if (hasEmail()) { + if (!getEmail() + .equals(other.getEmail())) return false; + } + if (!getPhonesList() + .equals(other.getPhonesList())) return false; + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasId()) { + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId(); + } + if (hasName()) { + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + } + if (hasEmail()) { + hash = (37 * hash) + EMAIL_FIELD_NUMBER; + hash = (53 * hash) + getEmail().hashCode(); + } + if (getPhonesCount() > 0) { + hash = (37 * hash) + PHONES_FIELD_NUMBER; + hash = (53 * hash) + getPhonesList().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.wdbyte.tool.protos.Person parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.Person parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.Person parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.Person parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.wdbyte.tool.protos.Person prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + *
+   * 可选消息字段类型:bool int32 float double string
+   * 或者自定义消息类型,如下面的 PhoneNumber
+   * 修饰符:optional: 可选字段,
+   * 修饰符:repeated:可重复,如数组。
+   * 修饰符:required:必要字段,必须给值,否则会报错 RuntimeException,但是在 protobuf 版本 3 中被移除。
+   * 慎用 required,因为一旦被标记 requieds,以后将不能更改,否则可能会出问题。
+   * 
+ * + * Protobuf type {@code com.wdbyte.protobuf.Person} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:com.wdbyte.protobuf.Person) + com.wdbyte.tool.protos.PersonOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.Person.class, com.wdbyte.tool.protos.Person.Builder.class); + } + + // Construct using com.wdbyte.tool.protos.Person.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = 0; + name_ = ""; + email_ = ""; + if (phonesBuilder_ == null) { + phones_ = java.util.Collections.emptyList(); + } else { + phones_ = null; + phonesBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.wdbyte.tool.protos.AddressBookProtos.internal_static_com_wdbyte_protobuf_Person_descriptor; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person getDefaultInstanceForType() { + return com.wdbyte.tool.protos.Person.getDefaultInstance(); + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person build() { + com.wdbyte.tool.protos.Person result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person buildPartial() { + com.wdbyte.tool.protos.Person result = new com.wdbyte.tool.protos.Person(this); + buildPartialRepeatedFields(result); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartialRepeatedFields(com.wdbyte.tool.protos.Person result) { + if (phonesBuilder_ == null) { + if (((bitField0_ & 0x00000008) != 0)) { + phones_ = java.util.Collections.unmodifiableList(phones_); + bitField0_ = (bitField0_ & ~0x00000008); + } + result.phones_ = phones_; + } else { + result.phones_ = phonesBuilder_.build(); + } + } + + private void buildPartial0(com.wdbyte.tool.protos.Person result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.name_ = name_; + to_bitField0_ |= 0x00000002; + } + if (((from_bitField0_ & 0x00000004) != 0)) { + result.email_ = email_; + to_bitField0_ |= 0x00000004; + } + result.bitField0_ |= to_bitField0_; + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.wdbyte.tool.protos.Person) { + return mergeFrom((com.wdbyte.tool.protos.Person)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.wdbyte.tool.protos.Person other) { + if (other == com.wdbyte.tool.protos.Person.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasName()) { + name_ = other.name_; + bitField0_ |= 0x00000002; + onChanged(); + } + if (other.hasEmail()) { + email_ = other.email_; + bitField0_ |= 0x00000004; + onChanged(); + } + if (phonesBuilder_ == null) { + if (!other.phones_.isEmpty()) { + if (phones_.isEmpty()) { + phones_ = other.phones_; + bitField0_ = (bitField0_ & ~0x00000008); + } else { + ensurePhonesIsMutable(); + phones_.addAll(other.phones_); + } + onChanged(); + } + } else { + if (!other.phones_.isEmpty()) { + if (phonesBuilder_.isEmpty()) { + phonesBuilder_.dispose(); + phonesBuilder_ = null; + phones_ = other.phones_; + bitField0_ = (bitField0_ & ~0x00000008); + phonesBuilder_ = + com.google.protobuf.GeneratedMessageV3.alwaysUseFieldBuilders ? + getPhonesFieldBuilder() : null; + } else { + phonesBuilder_.addAllMessages(other.phones_); + } + } + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + id_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 18: { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + case 26: { + email_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000004; + break; + } // case 26 + case 34: { + com.wdbyte.tool.protos.Person.PhoneNumber m = + input.readMessage( + com.wdbyte.tool.protos.Person.PhoneNumber.parser(), + extensionRegistry); + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + phones_.add(m); + } else { + phonesBuilder_.addMessage(m); + } + break; + } // case 34 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private int id_ ; + /** + * optional int32 id = 1; + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional int32 id = 1; + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + /** + * optional int32 id = 1; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId(int value) { + + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * optional int32 id = 1; + * @return This builder for chaining. + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + private java.lang.Object name_ = ""; + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + public boolean hasName() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string name = 2; + * @return The name. + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string name = 2; + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string name = 2; + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * optional string name = 2; + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * optional string name = 2; + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + + private java.lang.Object email_ = ""; + /** + * optional string email = 3; + * @return Whether the email field is set. + */ + public boolean hasEmail() { + return ((bitField0_ & 0x00000004) != 0); + } + /** + * optional string email = 3; + * @return The email. + */ + public java.lang.String getEmail() { + java.lang.Object ref = email_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + email_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string email = 3; + * @return The bytes for email. + */ + public com.google.protobuf.ByteString + getEmailBytes() { + java.lang.Object ref = email_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + email_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string email = 3; + * @param value The email to set. + * @return This builder for chaining. + */ + public Builder setEmail( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + email_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + /** + * optional string email = 3; + * @return This builder for chaining. + */ + public Builder clearEmail() { + email_ = getDefaultInstance().getEmail(); + bitField0_ = (bitField0_ & ~0x00000004); + onChanged(); + return this; + } + /** + * optional string email = 3; + * @param value The bytes for email to set. + * @return This builder for chaining. + */ + public Builder setEmailBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + email_ = value; + bitField0_ |= 0x00000004; + onChanged(); + return this; + } + + private java.util.List phones_ = + java.util.Collections.emptyList(); + private void ensurePhonesIsMutable() { + if (!((bitField0_ & 0x00000008) != 0)) { + phones_ = new java.util.ArrayList(phones_); + bitField0_ |= 0x00000008; + } + } + + private com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person.PhoneNumber, com.wdbyte.tool.protos.Person.PhoneNumber.Builder, com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder> phonesBuilder_; + + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public java.util.List getPhonesList() { + if (phonesBuilder_ == null) { + return java.util.Collections.unmodifiableList(phones_); + } else { + return phonesBuilder_.getMessageList(); + } + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public int getPhonesCount() { + if (phonesBuilder_ == null) { + return phones_.size(); + } else { + return phonesBuilder_.getCount(); + } + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public com.wdbyte.tool.protos.Person.PhoneNumber getPhones(int index) { + if (phonesBuilder_ == null) { + return phones_.get(index); + } else { + return phonesBuilder_.getMessage(index); + } + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder setPhones( + int index, com.wdbyte.tool.protos.Person.PhoneNumber value) { + if (phonesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePhonesIsMutable(); + phones_.set(index, value); + onChanged(); + } else { + phonesBuilder_.setMessage(index, value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder setPhones( + int index, com.wdbyte.tool.protos.Person.PhoneNumber.Builder builderForValue) { + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + phones_.set(index, builderForValue.build()); + onChanged(); + } else { + phonesBuilder_.setMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder addPhones(com.wdbyte.tool.protos.Person.PhoneNumber value) { + if (phonesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePhonesIsMutable(); + phones_.add(value); + onChanged(); + } else { + phonesBuilder_.addMessage(value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder addPhones( + int index, com.wdbyte.tool.protos.Person.PhoneNumber value) { + if (phonesBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + ensurePhonesIsMutable(); + phones_.add(index, value); + onChanged(); + } else { + phonesBuilder_.addMessage(index, value); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder addPhones( + com.wdbyte.tool.protos.Person.PhoneNumber.Builder builderForValue) { + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + phones_.add(builderForValue.build()); + onChanged(); + } else { + phonesBuilder_.addMessage(builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder addPhones( + int index, com.wdbyte.tool.protos.Person.PhoneNumber.Builder builderForValue) { + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + phones_.add(index, builderForValue.build()); + onChanged(); + } else { + phonesBuilder_.addMessage(index, builderForValue.build()); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder addAllPhones( + java.lang.Iterable values) { + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + com.google.protobuf.AbstractMessageLite.Builder.addAll( + values, phones_); + onChanged(); + } else { + phonesBuilder_.addAllMessages(values); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder clearPhones() { + if (phonesBuilder_ == null) { + phones_ = java.util.Collections.emptyList(); + bitField0_ = (bitField0_ & ~0x00000008); + onChanged(); + } else { + phonesBuilder_.clear(); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public Builder removePhones(int index) { + if (phonesBuilder_ == null) { + ensurePhonesIsMutable(); + phones_.remove(index); + onChanged(); + } else { + phonesBuilder_.remove(index); + } + return this; + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public com.wdbyte.tool.protos.Person.PhoneNumber.Builder getPhonesBuilder( + int index) { + return getPhonesFieldBuilder().getBuilder(index); + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( + int index) { + if (phonesBuilder_ == null) { + return phones_.get(index); } else { + return phonesBuilder_.getMessageOrBuilder(index); + } + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public java.util.List + getPhonesOrBuilderList() { + if (phonesBuilder_ != null) { + return phonesBuilder_.getMessageOrBuilderList(); + } else { + return java.util.Collections.unmodifiableList(phones_); + } + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public com.wdbyte.tool.protos.Person.PhoneNumber.Builder addPhonesBuilder() { + return getPhonesFieldBuilder().addBuilder( + com.wdbyte.tool.protos.Person.PhoneNumber.getDefaultInstance()); + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public com.wdbyte.tool.protos.Person.PhoneNumber.Builder addPhonesBuilder( + int index) { + return getPhonesFieldBuilder().addBuilder( + index, com.wdbyte.tool.protos.Person.PhoneNumber.getDefaultInstance()); + } + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + public java.util.List + getPhonesBuilderList() { + return getPhonesFieldBuilder().getBuilderList(); + } + private com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person.PhoneNumber, com.wdbyte.tool.protos.Person.PhoneNumber.Builder, com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder> + getPhonesFieldBuilder() { + if (phonesBuilder_ == null) { + phonesBuilder_ = new com.google.protobuf.RepeatedFieldBuilderV3< + com.wdbyte.tool.protos.Person.PhoneNumber, com.wdbyte.tool.protos.Person.PhoneNumber.Builder, com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder>( + phones_, + ((bitField0_ & 0x00000008) != 0), + getParentForChildren(), + isClean()); + phones_ = null; + } + return phonesBuilder_; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.wdbyte.protobuf.Person) + } + + // @@protoc_insertion_point(class_scope:com.wdbyte.protobuf.Person) + private static final com.wdbyte.tool.protos.Person DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.wdbyte.tool.protos.Person(); + } + + public static com.wdbyte.tool.protos.Person getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Person parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.wdbyte.tool.protos.Person getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + +} + diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/PersonOrBuilder.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/PersonOrBuilder.java new file mode 100644 index 0000000..6cfb178 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/PersonOrBuilder.java @@ -0,0 +1,78 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/addressbook.proto + +package com.wdbyte.tool.protos; + +public interface PersonOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.wdbyte.protobuf.Person) + com.google.protobuf.MessageOrBuilder { + + /** + * optional int32 id = 1; + * @return Whether the id field is set. + */ + boolean hasId(); + /** + * optional int32 id = 1; + * @return The id. + */ + int getId(); + + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + boolean hasName(); + /** + * optional string name = 2; + * @return The name. + */ + java.lang.String getName(); + /** + * optional string name = 2; + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + + /** + * optional string email = 3; + * @return Whether the email field is set. + */ + boolean hasEmail(); + /** + * optional string email = 3; + * @return The email. + */ + java.lang.String getEmail(); + /** + * optional string email = 3; + * @return The bytes for email. + */ + com.google.protobuf.ByteString + getEmailBytes(); + + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + java.util.List + getPhonesList(); + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + com.wdbyte.tool.protos.Person.PhoneNumber getPhones(int index); + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + int getPhonesCount(); + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + java.util.List + getPhonesOrBuilderList(); + /** + * repeated .com.wdbyte.protobuf.Person.PhoneNumber phones = 4; + */ + com.wdbyte.tool.protos.Person.PhoneNumberOrBuilder getPhonesOrBuilder( + int index); +} diff --git a/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentOuterClass.java b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentOuterClass.java new file mode 100644 index 0000000..c8c29e2 --- /dev/null +++ b/tool-java-protobuf/src/main/java/com/wdbyte/tool/protos/StudentOuterClass.java @@ -0,0 +1,770 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: resources/student.proto + +package com.wdbyte.tool.protos; + +public final class StudentOuterClass { + private StudentOuterClass() {} + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface StudentOrBuilder extends + // @@protoc_insertion_point(interface_extends:com.wdbyte.protobuf.Student) + com.google.protobuf.MessageOrBuilder { + + /** + * optional string id = 1; + * @return Whether the id field is set. + */ + boolean hasId(); + /** + * optional string id = 1; + * @return The id. + */ + java.lang.String getId(); + /** + * optional string id = 1; + * @return The bytes for id. + */ + com.google.protobuf.ByteString + getIdBytes(); + + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + boolean hasName(); + /** + * optional string name = 2; + * @return The name. + */ + java.lang.String getName(); + /** + * optional string name = 2; + * @return The bytes for name. + */ + com.google.protobuf.ByteString + getNameBytes(); + } + /** + * Protobuf type {@code com.wdbyte.protobuf.Student} + */ + public static final class Student extends + com.google.protobuf.GeneratedMessageV3 implements + // @@protoc_insertion_point(message_implements:com.wdbyte.protobuf.Student) + StudentOrBuilder { + private static final long serialVersionUID = 0L; + // Use Student.newBuilder() to construct. + private Student(com.google.protobuf.GeneratedMessageV3.Builder builder) { + super(builder); + } + private Student() { + id_ = ""; + name_ = ""; + } + + @java.lang.Override + @SuppressWarnings({"unused"}) + protected java.lang.Object newInstance( + UnusedPrivateParameter unused) { + return new Student(); + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.StudentOuterClass.internal_static_com_wdbyte_protobuf_Student_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.StudentOuterClass.internal_static_com_wdbyte_protobuf_Student_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.StudentOuterClass.Student.class, com.wdbyte.tool.protos.StudentOuterClass.Student.Builder.class); + } + + private int bitField0_; + public static final int ID_FIELD_NUMBER = 1; + @SuppressWarnings("serial") + private volatile java.lang.Object id_ = ""; + /** + * optional string id = 1; + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string id = 1; + * @return The id. + */ + @java.lang.Override + public java.lang.String getId() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } + } + /** + * optional string id = 1; + * @return The bytes for id. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + public static final int NAME_FIELD_NUMBER = 2; + @SuppressWarnings("serial") + private volatile java.lang.Object name_ = ""; + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + @java.lang.Override + public boolean hasName() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string name = 2; + * @return The name. + */ + @java.lang.Override + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } + } + /** + * optional string name = 2; + * @return The bytes for name. + */ + @java.lang.Override + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + com.google.protobuf.GeneratedMessageV3.writeString(output, 2, name_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.GeneratedMessageV3.computeStringSize(2, name_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof com.wdbyte.tool.protos.StudentOuterClass.Student)) { + return super.equals(obj); + } + com.wdbyte.tool.protos.StudentOuterClass.Student other = (com.wdbyte.tool.protos.StudentOuterClass.Student) obj; + + if (hasId() != other.hasId()) return false; + if (hasId()) { + if (!getId() + .equals(other.getId())) return false; + } + if (hasName() != other.hasName()) return false; + if (hasName()) { + if (!getName() + .equals(other.getName())) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasId()) { + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId().hashCode(); + } + if (hasName()) { + hash = (37 * hash) + NAME_FIELD_NUMBER; + hash = (53 * hash) + getName().hashCode(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input); + } + public static com.wdbyte.tool.protos.StudentOuterClass.Student parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessageV3 + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(com.wdbyte.tool.protos.StudentOuterClass.Student prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code com.wdbyte.protobuf.Student} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessageV3.Builder implements + // @@protoc_insertion_point(builder_implements:com.wdbyte.protobuf.Student) + com.wdbyte.tool.protos.StudentOuterClass.StudentOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return com.wdbyte.tool.protos.StudentOuterClass.internal_static_com_wdbyte_protobuf_Student_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internalGetFieldAccessorTable() { + return com.wdbyte.tool.protos.StudentOuterClass.internal_static_com_wdbyte_protobuf_Student_fieldAccessorTable + .ensureFieldAccessorsInitialized( + com.wdbyte.tool.protos.StudentOuterClass.Student.class, com.wdbyte.tool.protos.StudentOuterClass.Student.Builder.class); + } + + // Construct using com.wdbyte.tool.protos.StudentOuterClass.Student.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessageV3.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = ""; + name_ = ""; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return com.wdbyte.tool.protos.StudentOuterClass.internal_static_com_wdbyte_protobuf_Student_descriptor; + } + + @java.lang.Override + public com.wdbyte.tool.protos.StudentOuterClass.Student getDefaultInstanceForType() { + return com.wdbyte.tool.protos.StudentOuterClass.Student.getDefaultInstance(); + } + + @java.lang.Override + public com.wdbyte.tool.protos.StudentOuterClass.Student build() { + com.wdbyte.tool.protos.StudentOuterClass.Student result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public com.wdbyte.tool.protos.StudentOuterClass.Student buildPartial() { + com.wdbyte.tool.protos.StudentOuterClass.Student result = new com.wdbyte.tool.protos.StudentOuterClass.Student(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(com.wdbyte.tool.protos.StudentOuterClass.Student result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.name_ = name_; + to_bitField0_ |= 0x00000002; + } + result.bitField0_ |= to_bitField0_; + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof com.wdbyte.tool.protos.StudentOuterClass.Student) { + return mergeFrom((com.wdbyte.tool.protos.StudentOuterClass.Student)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(com.wdbyte.tool.protos.StudentOuterClass.Student other) { + if (other == com.wdbyte.tool.protos.StudentOuterClass.Student.getDefaultInstance()) return this; + if (other.hasId()) { + id_ = other.id_; + bitField0_ |= 0x00000001; + onChanged(); + } + if (other.hasName()) { + name_ = other.name_; + bitField0_ |= 0x00000002; + onChanged(); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 10: { + id_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000001; + break; + } // case 10 + case 18: { + name_ = input.readStringRequireUtf8(); + bitField0_ |= 0x00000002; + break; + } // case 18 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private java.lang.Object id_ = ""; + /** + * optional string id = 1; + * @return Whether the id field is set. + */ + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * optional string id = 1; + * @return The id. + */ + public java.lang.String getId() { + java.lang.Object ref = id_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + id_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string id = 1; + * @return The bytes for id. + */ + public com.google.protobuf.ByteString + getIdBytes() { + java.lang.Object ref = id_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + id_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string id = 1; + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * optional string id = 1; + * @return This builder for chaining. + */ + public Builder clearId() { + id_ = getDefaultInstance().getId(); + bitField0_ = (bitField0_ & ~0x00000001); + onChanged(); + return this; + } + /** + * optional string id = 1; + * @param value The bytes for id to set. + * @return This builder for chaining. + */ + public Builder setIdBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + + private java.lang.Object name_ = ""; + /** + * optional string name = 2; + * @return Whether the name field is set. + */ + public boolean hasName() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * optional string name = 2; + * @return The name. + */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * optional string name = 2; + * @return The bytes for name. + */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * optional string name = 2; + * @param value The name to set. + * @return This builder for chaining. + */ + public Builder setName( + java.lang.String value) { + if (value == null) { throw new NullPointerException(); } + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * optional string name = 2; + * @return This builder for chaining. + */ + public Builder clearName() { + name_ = getDefaultInstance().getName(); + bitField0_ = (bitField0_ & ~0x00000002); + onChanged(); + return this; + } + /** + * optional string name = 2; + * @param value The bytes for name to set. + * @return This builder for chaining. + */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { throw new NullPointerException(); } + checkByteStringIsUtf8(value); + name_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + @java.lang.Override + public final Builder setUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.setUnknownFields(unknownFields); + } + + @java.lang.Override + public final Builder mergeUnknownFields( + final com.google.protobuf.UnknownFieldSet unknownFields) { + return super.mergeUnknownFields(unknownFields); + } + + + // @@protoc_insertion_point(builder_scope:com.wdbyte.protobuf.Student) + } + + // @@protoc_insertion_point(class_scope:com.wdbyte.protobuf.Student) + private static final com.wdbyte.tool.protos.StudentOuterClass.Student DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new com.wdbyte.tool.protos.StudentOuterClass.Student(); + } + + public static com.wdbyte.tool.protos.StudentOuterClass.Student getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser + PARSER = new com.google.protobuf.AbstractParser() { + @java.lang.Override + public Student parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + @java.lang.Override + public com.wdbyte.tool.protos.StudentOuterClass.Student getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_com_wdbyte_protobuf_Student_descriptor; + private static final + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable + internal_static_com_wdbyte_protobuf_Student_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\027resources/student.proto\022\023com.wdbyte.pr" + + "otobuf\"=\n\007Student\022\017\n\002id\030\001 \001(\tH\000\210\001\001\022\021\n\004na" + + "me\030\002 \001(\tH\001\210\001\001B\005\n\003_idB\007\n\005_nameB\032\n\026com.wdb" + + "yte.tool.protosP\000b\006proto3" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_com_wdbyte_protobuf_Student_descriptor = + getDescriptor().getMessageTypes().get(0); + internal_static_com_wdbyte_protobuf_Student_fieldAccessorTable = new + com.google.protobuf.GeneratedMessageV3.FieldAccessorTable( + internal_static_com_wdbyte_protobuf_Student_descriptor, + new java.lang.String[] { "Id", "Name", "Id", "Name", }); + } + + // @@protoc_insertion_point(outer_class_scope) +} From 84bf5cb17fc989942280ef40931ebfe49b5affee Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 7 Jun 2023 19:12:12 +0800 Subject: [PATCH 066/105] =?UTF-8?q?feat:=20-=20[ProcessBuilder=20API=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=95=99=E7=A8=8B](https://www.wdbyte.com/ja?= =?UTF-8?q?va/os/processbuilder/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-os/.gitignore | 38 ++++++++++++++ core-java-modules/core-java-os/README.md | 6 +++ core-java-modules/core-java-os/pom.xml | 28 ++++++++++ .../java/com/wdbyte/os/process/ExecDemo.java | 18 +++++++ .../os/process/ProcessBuilderTest1.java | 52 +++++++++++++++++++ .../os/process/ProcessBuilderTest10.java | 42 +++++++++++++++ .../os/process/ProcessBuilderTest2.java | 32 ++++++++++++ .../os/process/ProcessBuilderTest3.java | 31 +++++++++++ .../os/process/ProcessBuilderTest4.java | 36 +++++++++++++ .../os/process/ProcessBuilderTest5.java | 40 ++++++++++++++ .../os/process/ProcessBuilderTest6.java | 23 ++++++++ .../os/process/ProcessBuilderTest7.java | 30 +++++++++++ .../os/process/ProcessBuilderTest8.java | 40 ++++++++++++++ .../os/process/ProcessBuilderTest9.java | 34 ++++++++++++ 14 files changed, 450 insertions(+) create mode 100644 core-java-modules/core-java-os/.gitignore create mode 100644 core-java-modules/core-java-os/README.md create mode 100644 core-java-modules/core-java-os/pom.xml create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ExecDemo.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest1.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest10.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest2.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest3.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest4.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest5.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest6.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest7.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest8.java create mode 100644 core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest9.java diff --git a/core-java-modules/core-java-os/.gitignore b/core-java-modules/core-java-os/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/core-java-modules/core-java-os/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/core-java-modules/core-java-os/README.md b/core-java-modules/core-java-os/README.md new file mode 100644 index 0000000..bfb1c8a --- /dev/null +++ b/core-java-modules/core-java-os/README.md @@ -0,0 +1,6 @@ +## core-java-os +当前模块包含操作系统操作相关代码 + +### 相关文章 + +- [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) \ No newline at end of file diff --git a/core-java-modules/core-java-os/pom.xml b/core-java-modules/core-java-os/pom.xml new file mode 100644 index 0000000..721644b --- /dev/null +++ b/core-java-modules/core-java-os/pom.xml @@ -0,0 +1,28 @@ + + + 4.0.0 + + com.wdbyte.core-java-modules + core-java-modules + 1.0.0-SNAPSHOT + + + core-java-os + + + 17 + 17 + UTF-8 + + + + + commons-io + commons-io + 2.12.0 + + + + \ No newline at end of file diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ExecDemo.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ExecDemo.java new file mode 100644 index 0000000..4b73eb6 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ExecDemo.java @@ -0,0 +1,18 @@ +package com.wdbyte.os.process; + +import java.io.IOException; + +/** + * @author https://www.wdbyte.com + */ +public class ExecDemo { + + public static void main(String[] args) throws InterruptedException { + System.out.println("开始处理数据..."); + for (int i = 0; i < 10; i++) { + Thread.sleep(1000); + System.out.println(i); + } + System.out.println("数据处理完毕"); + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest1.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest1.java new file mode 100644 index 0000000..919fb7f --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest1.java @@ -0,0 +1,52 @@ +package com.wdbyte.os.process; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.apache.commons.io.IOUtils; + +/** + * Process 输出Java 版本号 + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest1 { + + public static void main(String[] args) throws IOException, InterruptedException { + // 构建执行命令 + ProcessBuilder processBuilder = new ProcessBuilder("java","-version"); + // 重定向 ERROR 流(有些 JDK 版本 Java 命令通过 ERROR 流输出) + processBuilder.redirectErrorStream(true); + // 运行命令 java -version + Process process = processBuilder.start(); + // 过去PID + long pid = process.pid(); + // 一次性获取运行结果 + //String result = convertInputStreamToString(process.getInputStream()); + String result = IOUtils.toString(process.getInputStream()); + // 等到运行结束 + int exitCode = process.waitFor(); + + System.out.println("pid:" + pid); + System.out.println("result:" + result); + System.out.println("exitCode:" + exitCode); + } + + public static String convertInputStreamToString(InputStream inputStream) throws IOException { + StringBuilder sb = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) { + String line = null; + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + sb.append(System.lineSeparator()); + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + inputStream.close(); + } + return sb.toString(); + } + +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest10.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest10.java new file mode 100644 index 0000000..6c980d9 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest10.java @@ -0,0 +1,42 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +/** + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest10 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + processBuilder.command("java", "ExecDemo.java"); + // 把子线程 I/O 输出重定向当前进程 + processBuilder.inheritIO(); + + // 创建 CompletableFuture 对象 + CompletableFuture future = CompletableFuture.supplyAsync(() -> { + try { + // 命令执行 + Process process = processBuilder.start(); + // 任务超时时间 + process.waitFor(); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return null; + }); + + // 注册回调函数,处理异步等待的结果 + future.thenAccept(result -> { + System.out.println("进程执行结束"); + }); + System.out.println("主进程等待"); + Thread.sleep(20 * 1000); + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest2.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest2.java new file mode 100644 index 0000000..7075784 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest2.java @@ -0,0 +1,32 @@ +package com.wdbyte.os.process; + +import java.io.IOException; +import java.util.Map; + +import org.apache.commons.io.IOUtils; + +/** + * 修改环境变量 + * + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest2 { + + public static void main(String[] args) throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(); + Map environment = processBuilder.environment(); + environment.forEach((k, v) -> System.out.println(k + ":" + v)); + System.out.println("--------------"); + processBuilder.environment().put("my_website", "www.wdbyte.com"); + processBuilder.command("/bin/bash", "-c", "echo $my_website"); + + Process process = processBuilder.start(); + long pid = process.pid(); + String result = IOUtils.toString(process.getInputStream()); + int exitCode = process.waitFor(); + + System.out.println("pid:" + pid); + System.out.println("result:" + result); + System.out.println("exitCode:" + exitCode); + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest3.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest3.java new file mode 100644 index 0000000..3042923 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest3.java @@ -0,0 +1,31 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; + +import org.apache.commons.io.IOUtils; + +/** + * 修改工作目录 + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest3 { + + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + processBuilder.command("/bin/bash", "-c", "pwd"); + Process process = processBuilder.start(); + + long pid = process.pid(); + String result = IOUtils.toString(process.getInputStream()); + int exitCode = process.waitFor(); + + System.out.println("pid:" + pid); + System.out.println("result:" + result); + System.out.println("exitCode:" + exitCode); + } + +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest4.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest4.java new file mode 100644 index 0000000..73d91be --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest4.java @@ -0,0 +1,36 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +/** + * 输出日志到指定文件 + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest4 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + processBuilder.command("/bin/bash", "-c", "ls -l"); + + File logFile = new File(BASE_DIR + "/process_log.txt"); + // 输出到日志文件 + processBuilder.redirectOutput(logFile); + // 追加日志到文件 + // processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)); + // 是否输出ERROR日志到文件 + processBuilder.redirectErrorStream(true); + + Process process = processBuilder.start(); + long pid = process.pid(); + int exitCode = process.waitFor(); + System.out.println("pid:" + pid); + System.out.println("exitCode:" + exitCode); + + // 读取日志 + Files.lines(logFile.toPath()).forEach(System.out::println); + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest5.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest5.java new file mode 100644 index 0000000..54c4752 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest5.java @@ -0,0 +1,40 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +/** + * 输出日志到指定文件 + * + * @author https://www.wdbyte.com + ** + */ +public class ProcessBuilderTest5 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + // 执行命令 xxx,命令不存在,会报 ERROR 日志 + processBuilder.command("/bin/bash", "-c", "xxx"); + + File infoLogFile = new File(BASE_DIR + "/process_log_info.txt"); + File errorLogFile = new File(BASE_DIR + "/process_log_error.txt"); + // 日志输出到文件 + processBuilder.redirectOutput(infoLogFile); + processBuilder.redirectError(errorLogFile); + Process process = processBuilder.start(); + + long pid = process.pid(); + int exitCode = process.waitFor(); + + System.out.println("pid:" + pid); + System.out.println("exitCode:" + exitCode); + + // 读取 ERROR 日志 + Files.lines(errorLogFile.toPath()).forEach(System.out::println); + } + +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest6.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest6.java new file mode 100644 index 0000000..8c71d3b --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest6.java @@ -0,0 +1,23 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; + +/** + * 子线程 I/O 重定向到当前线程 + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest6 { + public static void main(String[] args) throws IOException, InterruptedException { + + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File("./")); + processBuilder.command("/bin/bash", "-c", "ls -l"); + // 把子线程 I/O 输出重定向当前进程 + processBuilder.inheritIO(); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + System.out.println("exitCode:" + exitCode); + } + +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest7.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest7.java new file mode 100644 index 0000000..bbcf647 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest7.java @@ -0,0 +1,30 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; + +/** + * 运行一个 Java 程序 + * + * @author https://www.wdbyte.com + ** + */ +public class ProcessBuilderTest7 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + processBuilder.command("java", "ExecDemo.java"); + // 把子线程 I/O 输出重定向当前进程 + processBuilder.inheritIO(); + Process process = processBuilder.start(); + + long pid = process.pid(); + int exitCode = process.waitFor(); + + System.out.println("pid:" + pid); + System.out.println("exitCode:" + exitCode); + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest8.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest8.java new file mode 100644 index 0000000..d38fbec --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest8.java @@ -0,0 +1,40 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; +import java.lang.ProcessBuilder.Redirect; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; + +/** + * Java 9 中新增的管道操作 + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest8 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + ProcessBuilder ls = new ProcessBuilder("/bin/bash", "-c", "ls -l"); + ProcessBuilder wc = new ProcessBuilder("wc", "-l"); + // 追加日志到文件 + File pipeLineLogFile = getFile(BASE_DIR + "/pipe_line_log.txt"); + wc.redirectOutput(Redirect.appendTo(pipeLineLogFile)); + + List processes = ProcessBuilder.startPipeline(Arrays.asList(ls, wc)); + Process process = processes.get(processes.size() - 1); + + System.out.println("pid:" + process.pid()); + System.out.println("exitCode:" + process.waitFor()); + + Files.lines(pipeLineLogFile.toPath()).forEach(System.out::println); + } + + public static File getFile(String filePath) throws IOException { + File logFile = new File(filePath); + if (!logFile.exists()) { + logFile.createNewFile(); + } + return logFile; + } +} diff --git a/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest9.java b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest9.java new file mode 100644 index 0000000..6ee7b23 --- /dev/null +++ b/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process/ProcessBuilderTest9.java @@ -0,0 +1,34 @@ +package com.wdbyte.os.process; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +/** + * 运行一个 Java 程序 + * 等待一定时间后检查状态,未结束则直接杀死进程。 + * + * @author https://www.wdbyte.com + */ +public class ProcessBuilderTest9 { + private static String BASE_DIR = "/Users/darcy/git/JavaNotes/core-java-modules/core-java-os/src/main/java/com/wdbyte/os/process"; + + public static void main(String[] args) throws IOException, InterruptedException { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.directory(new File(BASE_DIR)); + processBuilder.command("java", "ExecDemo.java"); + // 把子线程 I/O 输出重定向当前进程 + processBuilder.inheritIO(); + Process process = processBuilder.start(); + // 等待一定时间 + boolean waitFor = process.waitFor(3, TimeUnit.SECONDS); + System.out.println("waitFor:" + waitFor); + // 若未退出,杀死子进程 + if (!waitFor) { + process.destroyForcibly(); + process.waitFor(); + System.out.println("杀死进程:" + process); + } + + } +} From 2f49ceddbb88073fa747f3a1ba095e5b9629518d Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 7 Jun 2023 19:13:17 +0800 Subject: [PATCH 067/105] =?UTF-8?q?feat:=20-=20[ProcessBuilder=20API=20?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=95=99=E7=A8=8B](https://www.wdbyte.com/ja?= =?UTF-8?q?va/os/processbuilder/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ core-java-modules/README.md | 4 +++- core-java-modules/pom.xml | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2af0b44..543e7f9 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,9 @@ - [Java 枚举](https://www.wdbyte.com/java/enum/) - [Java 集合框架](https://www.wdbyte.com/java/collection/) +## Java 进阶 +- [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) + ## 🌿 SpringBoot 2.x 教程 使用 **Spring Boot** 可以快速的创建一个基于Spring 的、独立的、生产级的应用程序,并且可以直接运行。Spring Boot 采用习惯性配置,整合大量 Spring 组建和第三方库,让你只需要少量的修改就可以轻松上手。 diff --git a/core-java-modules/README.md b/core-java-modules/README.md index caefe7a..6a78bae 100644 --- a/core-java-modules/README.md +++ b/core-java-modules/README.md @@ -32,4 +32,6 @@ - [Java 中的 5 个代码性能提升技巧](https://www.wdbyte.com/java/code-5-tips.html) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) -- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) \ No newline at end of file +- [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) + +- [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) \ No newline at end of file diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 55a59a8..bce3cb7 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -28,6 +28,7 @@ core-java-18 core-java-collect core-java-base + core-java-os \ No newline at end of file From 2fa4fa8cc3c5b1fb1db94998f22eb2386f9ef8c0 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 16 Jun 2023 20:54:11 +0800 Subject: [PATCH 068/105] =?UTF-8?q?feat:=20-=20[=E4=BD=BF=E7=94=A8=20JComm?= =?UTF-8?q?ander=20=E8=A7=A3=E6=9E=90=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=8F=82?= =?UTF-8?q?=E6=95=B0](https://www.wdbyte.com/tool/jcommander/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tool-java-jcommander/.gitignore | 38 +++++++++++ tool-java-jcommander/README.md | 4 ++ tool-java-jcommander/pom.xml | 61 +++++++++++++++++ .../wdbyte/jcommander/FilePathConverter.java | 25 +++++++ .../java/com/wdbyte/jcommander/GitApp.java | 65 +++++++++++++++++++ .../com/wdbyte/jcommander/GitCommandAdd.java | 24 +++++++ .../wdbyte/jcommander/GitCommandCommit.java | 24 +++++++ .../wdbyte/jcommander/GitCommandOptions.java | 39 +++++++++++ .../java/com/wdbyte/jcommander/v1/GitApp.java | 20 ++++++ .../jcommander/v1/GitCommandOptions.java | 17 +++++ .../java/com/wdbyte/jcommander/v2/GitApp.java | 32 +++++++++ .../jcommander/v2/GitCommandOptions.java | 39 +++++++++++ .../java/com/wdbyte/jcommander/v3/GitApp.java | 32 +++++++++ .../jcommander/v3/GitCommandOptions.java | 40 ++++++++++++ .../jcommander/v3/UrlParameterValidator.java | 22 +++++++ .../java/com/wdbyte/jcommander/v4/GitApp.java | 38 +++++++++++ .../jcommander/v4/GitCommandCommit.java | 24 +++++++ .../jcommander/v4/GitCommandOptions.java | 40 ++++++++++++ .../jcommander/v4/UrlParameterValidator.java | 22 +++++++ .../java/com/wdbyte/jcommander/v5/GitApp.java | 45 +++++++++++++ .../wdbyte/jcommander/v5/GitCommandAdd.java | 23 +++++++ .../jcommander/v5/GitCommandCommit.java | 24 +++++++ .../jcommander/v5/GitCommandOptions.java | 40 ++++++++++++ .../jcommander/v5/UrlParameterValidator.java | 22 +++++++ 24 files changed, 760 insertions(+) create mode 100644 tool-java-jcommander/.gitignore create mode 100644 tool-java-jcommander/README.md create mode 100644 tool-java-jcommander/pom.xml create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java create mode 100644 tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java diff --git a/tool-java-jcommander/.gitignore b/tool-java-jcommander/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/tool-java-jcommander/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/tool-java-jcommander/README.md b/tool-java-jcommander/README.md new file mode 100644 index 0000000..d52fb9b --- /dev/null +++ b/tool-java-jcommander/README.md @@ -0,0 +1,4 @@ +## tool-java-jcommander + +### 相关文章 +- [使用 JCommander 解析命令行参数](https://www.wdbyte.com/tool/jcommander/) diff --git a/tool-java-jcommander/pom.xml b/tool-java-jcommander/pom.xml new file mode 100644 index 0000000..cb92565 --- /dev/null +++ b/tool-java-jcommander/pom.xml @@ -0,0 +1,61 @@ + + + 4.0.0 + + com.wdbyte + parent-modules + 1.0.0-SNAPSHOT + + + tool-java-jcommander + + + 1.8 + 1.8 + UTF-8 + + + + + com.beust + jcommander + 1.82 + + + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.1.0 + + + + com.wdbyte.jcommander.GitApp + + + + + jar-with-dependencies + + + git-app + + + + make-assembly + package + + single + + + + + + + + \ No newline at end of file diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java new file mode 100644 index 0000000..a451995 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java @@ -0,0 +1,25 @@ +package com.wdbyte.jcommander; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import com.beust.jcommander.IStringConverter; +import com.beust.jcommander.ParameterException; + +/** + * + * @author niulang + * @date 2023/06/15 + */ +public class FilePathConverter implements IStringConverter { + + @Override + public Path convert(String filePath) { + Path path = Paths.get(filePath); + if (Files.exists(path)) { + return path; + } + throw new ParameterException(String.format("文件不存在,path:%s", filePath)); + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java new file mode 100644 index 0000000..a9ca569 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java @@ -0,0 +1,65 @@ +package com.wdbyte.jcommander; + +import java.nio.file.Path; + +import com.beust.jcommander.JCommander; +import com.beust.jcommander.ParameterException; +import org.apache.commons.lang3.StringUtils; + +/** + * Git APP + * + * @author niulang + * @date 2023/06/07 + */ +public class GitApp { + private static GitCommandOptions commandOptions = new GitCommandOptions(); + private static GitCommandCommit commandCommit = new GitCommandCommit(); + private static GitCommandAdd commandAdd = new GitCommandAdd(); + private static JCommander commander; + + static { + commander = JCommander.newBuilder() + .programName("GitApp") + .addObject(commandOptions) + .addCommand(commandAdd) + .addCommand(commandCommit) + .build(); + } + + public static void main(String[] args) { + if (args.length == 0) { + commander.usage(); + return; + } + try { + commander.parse(args); + if (commandOptions.isHelp()) { + commander.usage(); + return; + } + if (commandOptions.isVersion()) { + System.out.println("git version 2.24.3 (Apple Git-128)"); + } + if (commandOptions.getCloneUrl() != null) { + System.out.printf("开始克隆远程仓库数据:%s%n", commandOptions.getCloneUrl()); + return; + } + String parsedCommand = commander.getParsedCommand(); + if (GitCommandCommit.COMMAND.equals(parsedCommand)) { + System.out.printf("提交暂存的文件并注释:%s%n", commandCommit.getComment()); + return; + } + if (GitCommandAdd.COMMAND.equals(parsedCommand)) { + for (Path file : commandAdd.getFiles()) { + System.out.printf("暂存文件:%s%n", file); + } + return; + } + } catch (ParameterException e) { + System.err.println(e.getMessage()); + commander.usage(); + } + } + +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java new file mode 100644 index 0000000..92d733e --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java @@ -0,0 +1,24 @@ +package com.wdbyte.jcommander; + +import java.nio.file.Path; +import java.util.List; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +/** + * git add file1 file2 + * + * @author niulang + * @date 2023/06/07 + */ +@Parameters(commandDescription = "暂存文件", commandNames = "add", separators = " ") +public class GitCommandAdd { + public static final String COMMAND = "add"; + @Parameter(description = "暂存文件列表", converter = FilePathConverter.class) + private List files; + + public List getFiles() { + return files; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java new file mode 100644 index 0000000..7758197 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java @@ -0,0 +1,24 @@ +package com.wdbyte.jcommander; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +/** + * git commit -m "desc" + * @author niulang + * @date 2023/06/07 + */ +@Parameters(commandDescription = "提交文件", commandNames = "commit") +public class GitCommandCommit { + public static final String COMMAND = "commit"; + + @Parameter(names = {"-comment", "-m"}, + description = "请输入注释", + arity = 1, + required = true) + private String comment; + + public String getComment() { + return comment; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java new file mode 100644 index 0000000..a845e72 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java @@ -0,0 +1,39 @@ +package com.wdbyte.jcommander; + +import java.net.URL; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.converters.URLConverter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + + @Parameter(names = {"help", "-help", "-h"}, + description = "查看帮助信息", + help = true) + private boolean help; + + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据", + arity = 1) + private String cloneUrl; + + @Parameter(names = {"version", "-version", "-v"}, + description = "显示当前版本号") + private boolean version = false; + + public boolean isHelp() { + return help; + } + + public boolean isVersion() { + return version; + } + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java new file mode 100644 index 0000000..1191d6f --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java @@ -0,0 +1,20 @@ +package com.wdbyte.jcommander.v1; + +import com.beust.jcommander.JCommander; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class GitApp { + + public static void main(String[] args) { + args = new String[]{"clone","http://www.wdbyte.com/test.git"}; + GitCommandOptions gitCommandOptions = new GitCommandOptions(); + JCommander commander = JCommander.newBuilder() + .addObject(gitCommandOptions) + .build(); + commander.parse(args); + System.out.println("clone " + gitCommandOptions.getCloneUrl()); + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java new file mode 100644 index 0000000..5f166b7 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java @@ -0,0 +1,17 @@ +package com.wdbyte.jcommander.v1; + +import com.beust.jcommander.Parameter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据") + private String cloneUrl; + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java new file mode 100644 index 0000000..e669602 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java @@ -0,0 +1,32 @@ +package com.wdbyte.jcommander.v2; + +import com.beust.jcommander.JCommander; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class GitApp { + + public static void main(String[] args) { + //args = new String[] {"clone", "http://www.wdbyte.com/test.git"}; + GitCommandOptions gitCommandOptions = new GitCommandOptions(); + JCommander commander = JCommander.newBuilder() + .programName("git-app") + .addObject(gitCommandOptions) + .build(); + commander.parse(args); + // 打印帮助信息 + if (gitCommandOptions.isHelp()) { + commander.usage(); + return; + } + if (gitCommandOptions.isVersion()) { + System.out.println("git version 2.24.3 (Apple Git-128)"); + return; + } + if (gitCommandOptions.getCloneUrl() != null) { + System.out.println("clone " + gitCommandOptions.getCloneUrl()); + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java new file mode 100644 index 0000000..50837be --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java @@ -0,0 +1,39 @@ +package com.wdbyte.jcommander.v2; + +import com.beust.jcommander.Parameter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + + @Parameter(names = {"help", "-help", "-h"}, + description = "查看帮助信息", + order = 1, + help = true) + private boolean help; + + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据", + order = 3, + arity = 1) + private String cloneUrl; + + @Parameter(names = {"version", "-version", "-v"}, + description = "显示当前版本号", + order = 2) + private boolean version = false; + + public boolean isHelp() { + return help; + } + + public boolean isVersion() { + return version; + } + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java new file mode 100644 index 0000000..8d1c7fd --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java @@ -0,0 +1,32 @@ +package com.wdbyte.jcommander.v3; + +import com.beust.jcommander.JCommander; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class GitApp { + + public static void main(String[] args) { + //args = new String[] {"clone", "ht2tp://www.wdbyte.com/test.git"}; + GitCommandOptions gitCommandOptions = new GitCommandOptions(); + JCommander commander = JCommander.newBuilder() + .programName("git-app") + .addObject(gitCommandOptions) + .build(); + commander.parse(args); + // 打印帮助信息 + if (gitCommandOptions.isHelp()) { + commander.usage(); + return; + } + if (gitCommandOptions.isVersion()) { + System.out.println("git version 2.24.3 (Apple Git-128)"); + return; + } + if (gitCommandOptions.getCloneUrl() != null) { + System.out.println("clone " + gitCommandOptions.getCloneUrl()); + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java new file mode 100644 index 0000000..fb0dc1b --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java @@ -0,0 +1,40 @@ +package com.wdbyte.jcommander.v3; + +import com.beust.jcommander.Parameter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + + @Parameter(names = {"help", "-help", "-h"}, + description = "查看帮助信息", + order = 1, + help = true) + private boolean help; + + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据", + validateWith = UrlParameterValidator.class, + order = 3, + arity = 1) + private String cloneUrl; + + @Parameter(names = {"version", "-version", "-v"}, + description = "显示当前版本号", + order = 2) + private boolean version = false; + + public boolean isHelp() { + return help; + } + + public boolean isVersion() { + return version; + } + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java new file mode 100644 index 0000000..4da9108 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java @@ -0,0 +1,22 @@ +package com.wdbyte.jcommander.v3; + +import java.net.MalformedURLException; +import java.net.URL; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class UrlParameterValidator implements IParameterValidator { + @Override + public void validate(String key, String value) throws ParameterException { + try { + new URL(value); + } catch (MalformedURLException e) { + throw new ParameterException("参数 " + key + " 的值必须是 URL 格式"); + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java new file mode 100644 index 0000000..1842cee --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java @@ -0,0 +1,38 @@ +package com.wdbyte.jcommander.v4; + +import com.beust.jcommander.JCommander; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class GitApp { + + public static void main(String[] args) { + //args = new String[] {"clone", "ht2tp://www.wdbyte.com/test.git"}; + GitCommandOptions gitCommandOptions = new GitCommandOptions(); + GitCommandCommit commandCommit = new GitCommandCommit(); + JCommander commander = JCommander.newBuilder() + .programName("git-app") + .addObject(gitCommandOptions) + .addCommand(commandCommit) + .build(); + commander.parse(args); + // 打印帮助信息 + if (gitCommandOptions.isHelp()) { + commander.usage(); + return; + } + if (gitCommandOptions.isVersion()) { + System.out.println("git version 2.24.3 (Apple Git-128)"); + return; + } + if (gitCommandOptions.getCloneUrl() != null) { + System.out.println("clone " + gitCommandOptions.getCloneUrl()); + } + String parsedCommand = commander.getParsedCommand(); + if ("commit".equals(parsedCommand)) { + System.out.println(commandCommit.getComment()); + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java new file mode 100644 index 0000000..7dd1c04 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java @@ -0,0 +1,24 @@ +package com.wdbyte.jcommander.v4; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +/** + * git commit -m "desc" + * @author niulang + * @date 2023/06/07 + */ +@Parameters(commandDescription = "提交文件", commandNames = "commit") +public class GitCommandCommit { + public static final String COMMAND = "commit"; + + @Parameter(names = {"-comment", "-m"}, + description = "请输入注释", + arity = 1, + required = true) + private String comment; + + public String getComment() { + return comment; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java new file mode 100644 index 0000000..86c02db --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java @@ -0,0 +1,40 @@ +package com.wdbyte.jcommander.v4; + +import com.beust.jcommander.Parameter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + + @Parameter(names = {"help", "-help", "-h"}, + description = "查看帮助信息", + order = 1, + help = true) + private boolean help; + + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据", + validateWith = UrlParameterValidator.class, + order = 3, + arity = 1) + private String cloneUrl; + + @Parameter(names = {"version", "-version", "-v"}, + description = "显示当前版本号", + order = 2) + private boolean version = false; + + public boolean isHelp() { + return help; + } + + public boolean isVersion() { + return version; + } + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java new file mode 100644 index 0000000..338e7c2 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java @@ -0,0 +1,22 @@ +package com.wdbyte.jcommander.v4; + +import java.net.MalformedURLException; +import java.net.URL; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class UrlParameterValidator implements IParameterValidator { + @Override + public void validate(String key, String value) throws ParameterException { + try { + new URL(value); + } catch (MalformedURLException e) { + throw new ParameterException("参数 " + key + " 的值必须是 URL 格式"); + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java new file mode 100644 index 0000000..7ecbc54 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java @@ -0,0 +1,45 @@ +package com.wdbyte.jcommander.v5; + +import com.beust.jcommander.JCommander; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class GitApp { + + public static void main(String[] args) { + //args = new String[] {"clone", "ht2tp://www.wdbyte.com/test.git"}; + GitCommandOptions gitCommandOptions = new GitCommandOptions(); + GitCommandCommit commandCommit = new GitCommandCommit(); + GitCommandAdd commandAdd = new GitCommandAdd(); + JCommander commander = JCommander.newBuilder() + .programName("git-app") + .addObject(gitCommandOptions) + .addCommand(commandCommit) + .addCommand(commandAdd) + .build(); + commander.parse(args); + // 打印帮助信息 + if (gitCommandOptions.isHelp()) { + commander.usage(); + return; + } + if (gitCommandOptions.isVersion()) { + System.out.println("git version 2.24.3 (Apple Git-128)"); + return; + } + if (gitCommandOptions.getCloneUrl() != null) { + System.out.println("clone " + gitCommandOptions.getCloneUrl()); + } + String parsedCommand = commander.getParsedCommand(); + if ("commit".equals(parsedCommand)) { + System.out.println(commandCommit.getComment()); + } + if ("add".equals(parsedCommand)) { + for (String file : commandAdd.getFiles()) { + System.out.println("暂存文件:" + file); + } + } + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java new file mode 100644 index 0000000..9e78ce6 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java @@ -0,0 +1,23 @@ +package com.wdbyte.jcommander.v5; + +import java.util.List; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +/** + * git add file1 file2 + * + * @author niulang + * @date 2023/06/07 + */ +@Parameters(commandDescription = "暂存文件", commandNames = "add", separators = " ") +public class GitCommandAdd { + public static final String COMMAND = "add"; + @Parameter(description = "暂存文件列表") + private List files; + + public List getFiles() { + return files; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java new file mode 100644 index 0000000..7a5f321 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java @@ -0,0 +1,24 @@ +package com.wdbyte.jcommander.v5; + +import com.beust.jcommander.Parameter; +import com.beust.jcommander.Parameters; + +/** + * git commit -m "desc" + * @author niulang + * @date 2023/06/07 + */ +@Parameters(commandDescription = "提交文件", commandNames = "commit") +public class GitCommandCommit { + public static final String COMMAND = "commit"; + + @Parameter(names = {"-comment", "-m"}, + description = "请输入注释", + arity = 1, + required = true) + private String comment; + + public String getComment() { + return comment; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java new file mode 100644 index 0000000..238aa39 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java @@ -0,0 +1,40 @@ +package com.wdbyte.jcommander.v5; + +import com.beust.jcommander.Parameter; + +/** + * @author niulang + * @date 2023/06/07 + */ +public class GitCommandOptions { + + @Parameter(names = {"help", "-help", "-h"}, + description = "查看帮助信息", + order = 1, + help = true) + private boolean help; + + @Parameter(names = {"clone"}, + description = "克隆远程仓库数据", + validateWith = UrlParameterValidator.class, + order = 3, + arity = 1) + private String cloneUrl; + + @Parameter(names = {"version", "-version", "-v"}, + description = "显示当前版本号", + order = 2) + private boolean version = false; + + public boolean isHelp() { + return help; + } + + public boolean isVersion() { + return version; + } + + public String getCloneUrl() { + return cloneUrl; + } +} diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java new file mode 100644 index 0000000..3527e80 --- /dev/null +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java @@ -0,0 +1,22 @@ +package com.wdbyte.jcommander.v5; + +import java.net.MalformedURLException; +import java.net.URL; + +import com.beust.jcommander.IParameterValidator; +import com.beust.jcommander.ParameterException; + +/** + * @author niulang + * @date 2023/06/15 + */ +public class UrlParameterValidator implements IParameterValidator { + @Override + public void validate(String key, String value) throws ParameterException { + try { + new URL(value); + } catch (MalformedURLException e) { + throw new ParameterException("参数 " + key + " 的值必须是 URL 格式"); + } + } +} From a6aa6a40d9a7e5d13c96152d2bf6d702df4bf124 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Fri, 16 Jun 2023 20:54:35 +0800 Subject: [PATCH 069/105] =?UTF-8?q?feat:=20-=20[=E4=BD=BF=E7=94=A8=20JComm?= =?UTF-8?q?ander=20=E8=A7=A3=E6=9E=90=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=8F=82?= =?UTF-8?q?=E6=95=B0](https://www.wdbyte.com/tool/jcommander/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ pom.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/README.md b/README.md index 543e7f9..3bbe625 100644 --- a/README.md +++ b/README.md @@ -187,6 +187,8 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 >出处:孔子《论语》 一款好用的工具,不仅可以装X,更可以让你事半功倍,准时下班。 + +- [使用 JCommander 解析命令行参数](https://www.wdbyte.com/tool/jcommander/) - [Protobuf 教程](https://www.wdbyte.com/tool/protobuf/) - [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html) - [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) diff --git a/pom.xml b/pom.xml index 2452dc4..eb573fb 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,7 @@ tool-java-apache-common tool-java-hotcode tool-java-protobuf + tool-java-jcommander parent-modules Parent for all java modules From 51e6cef3db5ed1eefb49b31e54a8554a986e9ac9 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Mon, 19 Jun 2023 16:17:48 +0800 Subject: [PATCH 070/105] =?UTF-8?q?docs:=20=E6=B3=A8=E9=87=8A=E5=90=8D?= =?UTF-8?q?=E7=A7=B0=E6=94=B9=E6=88=90=E7=BD=91=E5=9D=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/wdbyte/jcommander/FilePathConverter.java | 2 +- .../src/main/java/com/wdbyte/jcommander/GitApp.java | 2 +- .../src/main/java/com/wdbyte/jcommander/GitCommandAdd.java | 2 +- .../src/main/java/com/wdbyte/jcommander/GitCommandCommit.java | 2 +- .../src/main/java/com/wdbyte/jcommander/GitCommandOptions.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v1/GitApp.java | 2 +- .../main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v2/GitApp.java | 2 +- .../main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v3/GitApp.java | 2 +- .../main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java | 2 +- .../java/com/wdbyte/jcommander/v3/UrlParameterValidator.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v4/GitApp.java | 2 +- .../main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java | 2 +- .../main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java | 2 +- .../java/com/wdbyte/jcommander/v4/UrlParameterValidator.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v5/GitApp.java | 2 +- .../src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java | 2 +- .../main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java | 2 +- .../main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java | 2 +- .../java/com/wdbyte/jcommander/v5/UrlParameterValidator.java | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java index a451995..bf88b62 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/FilePathConverter.java @@ -9,7 +9,7 @@ /** * - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class FilePathConverter implements IStringConverter { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java index a9ca569..20203c7 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitApp.java @@ -9,7 +9,7 @@ /** * Git APP * - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java index 92d733e..7fc9ff1 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandAdd.java @@ -9,7 +9,7 @@ /** * git add file1 file2 * - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ @Parameters(commandDescription = "暂存文件", commandNames = "add", separators = " ") diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java index 7758197..6710a63 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandCommit.java @@ -5,7 +5,7 @@ /** * git commit -m "desc" - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ @Parameters(commandDescription = "提交文件", commandNames = "commit") diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java index a845e72..f9cc388 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/GitCommandOptions.java @@ -6,7 +6,7 @@ import com.beust.jcommander.converters.URLConverter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java index 1191d6f..f03e882 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitApp.java @@ -3,7 +3,7 @@ import com.beust.jcommander.JCommander; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java index 5f166b7..8e986de 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v1/GitCommandOptions.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java index e669602..6271d88 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitApp.java @@ -3,7 +3,7 @@ import com.beust.jcommander.JCommander; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java index 50837be..cff5d4d 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v2/GitCommandOptions.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java index 8d1c7fd..9350713 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitApp.java @@ -3,7 +3,7 @@ import com.beust.jcommander.JCommander; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java index fb0dc1b..fd75160 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/GitCommandOptions.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java index 4da9108..6ff98fa 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v3/UrlParameterValidator.java @@ -7,7 +7,7 @@ import com.beust.jcommander.ParameterException; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class UrlParameterValidator implements IParameterValidator { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java index 1842cee..33d3dfb 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitApp.java @@ -3,7 +3,7 @@ import com.beust.jcommander.JCommander; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java index 7dd1c04..934c723 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandCommit.java @@ -5,7 +5,7 @@ /** * git commit -m "desc" - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ @Parameters(commandDescription = "提交文件", commandNames = "commit") diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java index 86c02db..32dcc40 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/GitCommandOptions.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java index 338e7c2..817bb7f 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v4/UrlParameterValidator.java @@ -7,7 +7,7 @@ import com.beust.jcommander.ParameterException; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class UrlParameterValidator implements IParameterValidator { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java index 7ecbc54..a7ee225 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitApp.java @@ -3,7 +3,7 @@ import com.beust.jcommander.JCommander; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class GitApp { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java index 9e78ce6..e1846f3 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandAdd.java @@ -8,7 +8,7 @@ /** * git add file1 file2 * - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ @Parameters(commandDescription = "暂存文件", commandNames = "add", separators = " ") diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java index 7a5f321..6b76c65 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandCommit.java @@ -5,7 +5,7 @@ /** * git commit -m "desc" - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ @Parameters(commandDescription = "提交文件", commandNames = "commit") diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java index 238aa39..0f3b3ee 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/GitCommandOptions.java @@ -3,7 +3,7 @@ import com.beust.jcommander.Parameter; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/07 */ public class GitCommandOptions { diff --git a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java index 3527e80..cda4988 100644 --- a/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java +++ b/tool-java-jcommander/src/main/java/com/wdbyte/jcommander/v5/UrlParameterValidator.java @@ -7,7 +7,7 @@ import com.beust.jcommander.ParameterException; /** - * @author niulang + * @author https://www.wdbyte.com * @date 2023/06/15 */ public class UrlParameterValidator implements IParameterValidator { From 1f416f76fa68b92fe1c52396951888c77ba8fc7b Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 19 Jun 2023 22:44:07 +0800 Subject: [PATCH 071/105] =?UTF-8?q?feat:=20=E6=8E=A5=E5=8F=A3=E5=92=8C?= =?UTF-8?q?=E6=8A=BD=E8=B1=A1=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/wdbyte/oop/abs/AbsPerson2.java | 13 +++++++++++++ .../com/wdbyte/oop/interfac/InterfacePerson.java | 9 +++++++++ 2 files changed, 22 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/InterfacePerson.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson2.java new file mode 100644 index 0000000..2617065 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/abs/AbsPerson2.java @@ -0,0 +1,13 @@ +package com.wdbyte.oop.abs; + +/** + * @author https://www.wdbyte.com + */ +public abstract class AbsPerson2 { + + public void sayHello() { + System.out.println("say hello"); + } + + public abstract void sleep(); +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/InterfacePerson.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/InterfacePerson.java new file mode 100644 index 0000000..0cd8562 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/oop/interfac/InterfacePerson.java @@ -0,0 +1,9 @@ +package com.wdbyte.oop.interfac; + +public interface InterfacePerson { + default void sayHello(){ + System.out.println("say hello"); + } + + void sleep(); +} From 7c506d3ddaba2cca49bfa047b5e6629353359ffb Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 21 Jun 2023 17:12:06 +0800 Subject: [PATCH 072/105] =?UTF-8?q?feat:=20Java=20=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wdbyte/comment/JavaDocDemo.java | 41 ++ .../java/com/wdbyte/comment/WelcomeMain.java | 51 ++ .../wdbyte/comment/doc/allclasses-frame.html | 19 + .../comment/doc/allclasses-noframe.html | 19 + .../doc/com/wdbyte/comment/JavaDocDemo.html | 310 ++++++++++ .../doc/com/wdbyte/comment/package-frame.html | 20 + .../com/wdbyte/comment/package-summary.html | 140 +++++ .../doc/com/wdbyte/comment/package-tree.html | 129 ++++ .../wdbyte/comment/doc/constant-values.html | 120 ++++ .../wdbyte/comment/doc/deprecated-list.html | 120 ++++ .../java/com/wdbyte/comment/doc/help-doc.html | 217 +++++++ .../com/wdbyte/comment/doc/index-all.html | 157 +++++ .../java/com/wdbyte/comment/doc/index.html | 72 +++ .../com/wdbyte/comment/doc/overview-tree.html | 133 ++++ .../java/com/wdbyte/comment/doc/package-list | 1 + .../java/com/wdbyte/comment/doc/script.js | 30 + .../com/wdbyte/comment/doc/stylesheet.css | 574 ++++++++++++++++++ 17 files changed, 2153 insertions(+) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/JavaDocDemo.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/WelcomeMain.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-frame.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-noframe.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/JavaDocDemo.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-frame.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-summary.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-tree.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/constant-values.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/deprecated-list.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/help-doc.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index-all.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/overview-tree.html create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/package-list create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/script.js create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/stylesheet.css diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/JavaDocDemo.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/JavaDocDemo.java new file mode 100644 index 0000000..0625952 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/JavaDocDemo.java @@ -0,0 +1,41 @@ +package com.wdbyte.comment; + +/** + * 输出一个名称和地域的问候信息。 + * 如:Hello 朋友,welcome to 杭州 + * 主要实现方法是 {@link JavaDocDemo#getMessage(String, String)} + * + * @author wdbyte + * @version 1.0 + * @see com.wdbyte.comment.JavaDocDemo#getMessage(String, String) + * @since 1.0 + */ +public class JavaDocDemo { + + /** + * 启动应用程序 + * + * @param args - 应用启动参数 + */ + public static void main(String[] args) { + System.out.println(getMessage("朋友", "杭州")); + } + + /** + * 返回一个欢迎信息。 + * @see Java Dcoumentation + * @param name - 访问者名称 + * @param region - 地域信息 + * @return - 欢迎信息语句 + */ + public static String getMessage(String name, String region) { + StringBuilder builder = new StringBuilder(); + builder.append("Hello "); + builder.append(name); + builder.append(", Welcome to "); + builder.append(region); + builder.append(" !!"); + return builder.toString(); + } + +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/WelcomeMain.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/WelcomeMain.java new file mode 100644 index 0000000..153bed9 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/WelcomeMain.java @@ -0,0 +1,51 @@ +package com.wdbyte.comment; + +/** + * 输出一个名称和地域的问候信息。 + * 如:Hello 朋友,welcome to 杭州 + * + * @author https://www.wdbyte.com + */ +public class WelcomeMain { + + /** + * 启动应用程序 + * + * @param args - 应用启动参数 + */ + public static void main(String[] args) { + System.out.println(getMessage("朋友", "杭州")); + } + + /** + * 返回一个欢迎信息。 + * + * @param name - 访问者名称 + * @param region - 地域信息 + * @return - 欢迎信息 + */ + public static String getMessage(String name, String region) { + StringBuilder builder = new StringBuilder(); + builder.append("Hello "); + builder.append(name); + builder.append(", Welcome to "); + builder.append(region); + builder.append(" !!"); + return builder.toString(); + } + + + /** + * 计算两数之和 + * @param x 数字1 + * @param y 数字2 + * @return + */ + public int add(int x, int y) { + /* + * 计算两数之和 + */ + int s = x + y; + return s; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-frame.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-frame.html new file mode 100644 index 0000000..d1e5c11 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-frame.html @@ -0,0 +1,19 @@ + + + + + +所有类 + + + + + +

所有类

+
+ +
+ + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-noframe.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-noframe.html new file mode 100644 index 0000000..4e6a6c4 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/allclasses-noframe.html @@ -0,0 +1,19 @@ + + + + + +所有类 + + + + + +

所有类

+
+ +
+ + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/JavaDocDemo.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/JavaDocDemo.html new file mode 100644 index 0000000..12f6a1c --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/JavaDocDemo.html @@ -0,0 +1,310 @@ + + + + + +JavaDocDemo + + + + + + + + + + + + +
+
com.wdbyte.comment
+

类 JavaDocDemo

+
+
+
    +
  • java.lang.Object
  • +
  • +
      +
    • com.wdbyte.comment.JavaDocDemo
    • +
    +
  • +
+
+
    +
  • +
    +
    +
    public class JavaDocDemo
    +extends java.lang.Object
    +
    输出一个名称和地域的问候信息。 + 如:Hello 朋友,welcome to 杭州 + 主要实现方法是 getMessage(String, String)
    +
    +
    从以下版本开始:
    +
    1.0
    +
    另请参阅:
    +
    getMessage(String, String)
    +
    +
  • +
+
+
+
    +
  • + +
      +
    • + + +

      构造器概要

      +
+ + + + + + + +
构造器 
构造器和说明
JavaDocDemo() 
+ + + +

    +
  • + + +

    方法概要

    + + + + + + + + + + + + + + +
    所有方法 静态方法 具体方法 
    限定符和类型方法和说明
    static java.lang.StringgetMessage(java.lang.String name, + java.lang.String region) +
    返回一个欢迎信息。
    +
    static voidmain(java.lang.String[] args) +
    启动应用程序
    +
    +
      +
    • + + +

      从类继承的方法 java.lang.Object

      +clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
    • +
    +
  • +
+ + + +
+
    +
  • + +
      +
    • + + +

      构造器详细资料

      + + + +
        +
      • +

        JavaDocDemo

        +
        public JavaDocDemo()
        +
      • +
      +
    • +
    + +
      +
    • + + +

      方法详细资料

      + + + +
        +
      • +

        main

        +
        public static void main(java.lang.String[] args)
        +
        启动应用程序
        +
        +
        参数:
        +
        args - - 应用启动参数
        +
        +
      • +
      + + + +
        +
      • +

        getMessage

        +
        public static java.lang.String getMessage(java.lang.String name,
        +                                          java.lang.String region)
        +
        返回一个欢迎信息。
        +
        +
        参数:
        +
        name - - 访问者名称
        +
        region - - 地域信息
        +
        返回:
        +
        - 欢迎信息语句
        +
        另请参阅:
        +
        Java Dcoumentation
        +
        +
      • +
      +
    • +
    +
  • +
+
+ + + + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-frame.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-frame.html new file mode 100644 index 0000000..cafb6ef --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-frame.html @@ -0,0 +1,20 @@ + + + + + +com.wdbyte.comment + + + + + +

com.wdbyte.comment

+
+

+ +
+ + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-summary.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-summary.html new file mode 100644 index 0000000..14848c8 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-summary.html @@ -0,0 +1,140 @@ + + + + + +com.wdbyte.comment + + + + + + + + + + + +
+

程序包 com.wdbyte.comment

+
+
+
    +
  • + + + + + + + + + + + + +
    类概要 
    说明
    JavaDocDemo +
    输出一个名称和地域的问候信息。
    +
    +
  • +
+
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-tree.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-tree.html new file mode 100644 index 0000000..dad782d --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/com/wdbyte/comment/package-tree.html @@ -0,0 +1,129 @@ + + + + + +com.wdbyte.comment 类分层结构 + + + + + + + + + + + +
+

程序包com.wdbyte.comment的分层结构

+
+
+

类分层结构

+
    +
  • java.lang.Object + +
  • +
+
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/constant-values.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/constant-values.html new file mode 100644 index 0000000..be8d957 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/constant-values.html @@ -0,0 +1,120 @@ + + + + + +常量字段值 + + + + + + + + + + + +
+

常量字段值

+

目录

+
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/deprecated-list.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/deprecated-list.html new file mode 100644 index 0000000..a03d078 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/deprecated-list.html @@ -0,0 +1,120 @@ + + + + + +已过时的列表 + + + + + + + + +
+ + + + + + + +
+ + +
+

已过时的 API

+

目录

+
+ +
+ + + + + + + +
+ + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/help-doc.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/help-doc.html new file mode 100644 index 0000000..b96669f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/help-doc.html @@ -0,0 +1,217 @@ + + + + + +API 帮助 + + + + + + + + + + + +
+

此 API 文档的组织方式

+
此 API (应用程序编程接口) 文档包含对应于导航栏中的项目的页面, 如下所述。
+
+
+
    +
  • +

    程序包

    +

    每个程序包都有一个页面, 其中包含它的类和接口的列表及其概要。此页面可以包含六个类别:

    +
      +
    • 接口 (斜体)
    • +
    • +
    • 枚举
    • +
    • 异常错误
    • +
    • 错误
    • +
    • 注释类型
    • +
    +
  • +
  • +

    类/接口

    +

    每个类, 接口, 嵌套类和嵌套接口都有各自的页面。其中每个页面都由三部分 (类/接口说明, 概要表, 以及详细的成员说明) 组成:

    +
      +
    • 类继承图
    • +
    • 直接子类
    • +
    • 所有已知子接口
    • +
    • 所有已知实现类
    • +
    • 类/接口声明
    • +
    • 类/接口说明
    • +
    +
      +
    • 嵌套类概要
    • +
    • 字段概要
    • +
    • 构造器概要
    • +
    • 方法概要
    • +
    +
      +
    • 字段详细资料
    • +
    • 构造器详细资料
    • +
    • 方法详细资料
    • +
    +

    每个概要条目都包含该项目的详细说明的第一句。概要条目按字母顺序排列, 而详细说明则按其在源代码中出现的顺序排列。这样保持了程序员所建立的逻辑分组。

    +
  • +
  • +

    注释类型

    +

    每个注释类型都有各自的页面, 其中包含以下部分:

    +
      +
    • 注释类型声明
    • +
    • 注释类型说明
    • +
    • 必需元素概要
    • +
    • 可选元素概要
    • +
    • 元素详细资料
    • +
    +
  • +
  • +

    枚举

    +

    每个枚举都有各自的页面, 其中包含以下部分:

    +
      +
    • 枚举声明
    • +
    • 枚举说明
    • +
    • 枚举常量概要
    • +
    • 枚举常量详细资料
    • +
    +
  • +
  • +

    树 (类分层结构)

    +

    对于所有程序包, 有一个类分层结构页面, 以及每个程序包的分层结构。每个分层结构页面都包含类的列表和接口的列表。从java.lang.Object开始, 按继承结构对类进行排列。接口不从java.lang.Object继承。

    +
      +
    • 查看“概览”页面时, 单击 "树" 将显示所有程序包的分层结构。
    • +
    • 查看特定程序包, 类或接口页面时, 单击 "树" 将仅显示该程序包的分层结构。
    • +
    +
  • +
  • +

    已过时的 API

    +

    已过时的 API 页面列出了所有已过时的 API。一般由于进行了改进并且通常提供了替代的 API, 所以建议不要使用已过时的 API。在将来的实现过程中, 可能会删除已过时的 API。

    +
  • +
  • +

    索引

    +

    索引 包含按字母顺序排列的所有类, 接口, 构造器, 方法和字段的列表。

    +
  • +
  • +

    上一个/下一个

    +

    这些链接使您可以转至下一个或上一个类, 接口, 程序包或相关页面。

    +
  • +
  • +

    框架/无框架

    +

    这些链接用于显示和隐藏 HTML 框架。所有页面均具有有框架和无框架两种显示方式。

    +
  • +
  • +

    所有类

    +

    所有类链接显示所有类和接口 (除了非静态嵌套类型)。

    +
  • +
  • +

    序列化表格

    +

    每个可序列化或可外部化的类都有其序列化字段和方法的说明。此信息对重新实现者有用, 而对使用 API 的开发者则没有什么用处。尽管导航栏中没有链接, 但您可以通过下列方式获取此信息: 转至任何序列化类, 然后单击类说明的 "另请参阅" 部分中的 "序列化表格"。

    +
  • +
  • +

    常量字段值

    +

    常量字段值页面列出了静态最终字段及其值。

    +
  • +
+此帮助文件适用于使用标准 doclet 生成的 API 文档。
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index-all.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index-all.html new file mode 100644 index 0000000..06a699f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index-all.html @@ -0,0 +1,157 @@ + + + + + +索引 + + + + + + + + + + + +
C G J M  + + +

C

+
+
com.wdbyte.comment - 程序包 com.wdbyte.comment
+
 
+
+ + + +

G

+
+
getMessage(String, String) - 类 中的静态方法com.wdbyte.comment.JavaDocDemo
+
+
返回一个欢迎信息。
+
+
+ + + +

J

+
+
JavaDocDemo - com.wdbyte.comment中的类
+
+
输出一个名称和地域的问候信息。
+
+
JavaDocDemo() - 类 的构造器com.wdbyte.comment.JavaDocDemo
+
 
+
+ + + +

M

+
+
main(String[]) - 类 中的静态方法com.wdbyte.comment.JavaDocDemo
+
+
启动应用程序
+
+
+C G J M 
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index.html new file mode 100644 index 0000000..720515c --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/index.html @@ -0,0 +1,72 @@ + + + + + +生成的文档 (无标题) + + + + + + +<noscript> +<div>您的浏览器已禁用 JavaScript。</div> +</noscript> +<h2>框架预警</h2> +<p>请使用框架功能查看此文档。如果看到此消息, 则表明您使用的是不支持框架的 Web 客户机。链接到<a href="com/wdbyte/comment/package-summary.html">非框架版本</a>。</p> + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/overview-tree.html b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/overview-tree.html new file mode 100644 index 0000000..50e2869 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/overview-tree.html @@ -0,0 +1,133 @@ + + + + + +类分层结构 + + + + + + + + + + + +
+

所有程序包的分层结构

+程序包分层结构: + +
+
+

类分层结构

+
    +
  • java.lang.Object + +
  • +
+
+ + + + + + diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/package-list b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/package-list new file mode 100644 index 0000000..84f8f0f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/package-list @@ -0,0 +1 @@ +com.wdbyte.comment diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/script.js b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/script.js new file mode 100644 index 0000000..b346356 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/script.js @@ -0,0 +1,30 @@ +function show(type) +{ + count = 0; + for (var key in methods) { + var row = document.getElementById(key); + if ((methods[key] & type) != 0) { + row.style.display = ''; + row.className = (count++ % 2) ? rowColor : altColor; + } + else + row.style.display = 'none'; + } + updateTabs(type); +} + +function updateTabs(type) +{ + for (var value in tabs) { + var sNode = document.getElementById(tabs[value][0]); + var spanNode = sNode.firstChild; + if (value == type) { + sNode.className = activeTableTab; + spanNode.innerHTML = tabs[value][1]; + } + else { + sNode.className = tableTab; + spanNode.innerHTML = "" + tabs[value][1] + ""; + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/stylesheet.css b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/stylesheet.css new file mode 100644 index 0000000..98055b2 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/comment/doc/stylesheet.css @@ -0,0 +1,574 @@ +/* Javadoc style sheet */ +/* +Overall document style +*/ + +@import url('resources/fonts/dejavu.css'); + +body { + background-color:#ffffff; + color:#353833; + font-family:'DejaVu Sans', Arial, Helvetica, sans-serif; + font-size:14px; + margin:0; +} +a:link, a:visited { + text-decoration:none; + color:#4A6782; +} +a:hover, a:focus { + text-decoration:none; + color:#bb7a2a; +} +a:active { + text-decoration:none; + color:#4A6782; +} +a[name] { + color:#353833; +} +a[name]:hover { + text-decoration:none; + color:#353833; +} +pre { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; +} +h1 { + font-size:20px; +} +h2 { + font-size:18px; +} +h3 { + font-size:16px; + font-style:italic; +} +h4 { + font-size:13px; +} +h5 { + font-size:12px; +} +h6 { + font-size:11px; +} +ul { + list-style-type:disc; +} +code, tt { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; + margin-top:8px; + line-height:1.4em; +} +dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + padding-top:4px; +} +table tr td dt code { + font-family:'DejaVu Sans Mono', monospace; + font-size:14px; + vertical-align:top; + padding-top:4px; +} +sup { + font-size:8px; +} +/* +Document title and Copyright styles +*/ +.clear { + clear:both; + height:0px; + overflow:hidden; +} +.aboutLanguage { + float:right; + padding:0px 21px; + font-size:11px; + z-index:200; + margin-top:-9px; +} +.legalCopy { + margin-left:.5em; +} +.bar a, .bar a:link, .bar a:visited, .bar a:active { + color:#FFFFFF; + text-decoration:none; +} +.bar a:hover, .bar a:focus { + color:#bb7a2a; +} +.tab { + background-color:#0066FF; + color:#ffffff; + padding:8px; + width:5em; + font-weight:bold; +} +/* +Navigation bar styles +*/ +.bar { + background-color:#4D7A97; + color:#FFFFFF; + padding:.8em .5em .4em .8em; + height:auto;/*height:1.8em;*/ + font-size:11px; + margin:0; +} +.topNav { + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.bottomNav { + margin-top:10px; + background-color:#4D7A97; + color:#FFFFFF; + float:left; + padding:0; + width:100%; + clear:right; + height:2.8em; + padding-top:10px; + overflow:hidden; + font-size:12px; +} +.subNav { + background-color:#dee3e9; + float:left; + width:100%; + overflow:hidden; + font-size:12px; +} +.subNav div { + clear:left; + float:left; + padding:0 0 5px 6px; + text-transform:uppercase; +} +ul.navList, ul.subNavList { + float:left; + margin:0 25px 0 0; + padding:0; +} +ul.navList li{ + list-style:none; + float:left; + padding: 5px 6px; + text-transform:uppercase; +} +ul.subNavList li{ + list-style:none; + float:left; +} +.topNav a:link, .topNav a:active, .topNav a:visited, .bottomNav a:link, .bottomNav a:active, .bottomNav a:visited { + color:#FFFFFF; + text-decoration:none; + text-transform:uppercase; +} +.topNav a:hover, .bottomNav a:hover { + text-decoration:none; + color:#bb7a2a; + text-transform:uppercase; +} +.navBarCell1Rev { + background-color:#F8981D; + color:#253441; + margin: auto 5px; +} +.skipNav { + position:absolute; + top:auto; + left:-9999px; + overflow:hidden; +} +/* +Page header and footer styles +*/ +.header, .footer { + clear:both; + margin:0 20px; + padding:5px 0 0 0; +} +.indexHeader { + margin:10px; + position:relative; +} +.indexHeader span{ + margin-right:15px; +} +.indexHeader h1 { + font-size:13px; +} +.title { + color:#2c4557; + margin:10px 0; +} +.subTitle { + margin:5px 0 0 0; +} +.header ul { + margin:0 0 15px 0; + padding:0; +} +.footer ul { + margin:20px 0 5px 0; +} +.header ul li, .footer ul li { + list-style:none; + font-size:13px; +} +/* +Heading styles +*/ +div.details ul.blockList ul.blockList ul.blockList li.blockList h4, div.details ul.blockList ul.blockList ul.blockListLast li.blockList h4 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList ul.blockList li.blockList h3 { + background-color:#dee3e9; + border:1px solid #d0d9e0; + margin:0 0 6px -8px; + padding:7px 5px; +} +ul.blockList ul.blockList li.blockList h3 { + padding:0; + margin:15px 0; +} +ul.blockList li.blockList h2 { + padding:0px 0 20px 0; +} +/* +Page layout container styles +*/ +.contentContainer, .sourceContainer, .classUseContainer, .serializedFormContainer, .constantValuesContainer { + clear:both; + padding:10px 20px; + position:relative; +} +.indexContainer { + margin:10px; + position:relative; + font-size:12px; +} +.indexContainer h2 { + font-size:13px; + padding:0 0 3px 0; +} +.indexContainer ul { + margin:0; + padding:0; +} +.indexContainer ul li { + list-style:none; + padding-top:2px; +} +.contentContainer .description dl dt, .contentContainer .details dl dt, .serializedFormContainer dl dt { + font-size:12px; + font-weight:bold; + margin:10px 0 0 0; + color:#4E4E4E; +} +.contentContainer .description dl dd, .contentContainer .details dl dd, .serializedFormContainer dl dd { + margin:5px 0 10px 0px; + font-size:14px; + font-family:'DejaVu Sans Mono',monospace; +} +.serializedFormContainer dl.nameValue dt { + margin-left:1px; + font-size:1.1em; + display:inline; + font-weight:bold; +} +.serializedFormContainer dl.nameValue dd { + margin:0 0 0 1px; + font-size:1.1em; + display:inline; +} +/* +List styles +*/ +ul.horizontal li { + display:inline; + font-size:0.9em; +} +ul.inheritance { + margin:0; + padding:0; +} +ul.inheritance li { + display:inline; + list-style:none; +} +ul.inheritance li ul.inheritance { + margin-left:15px; + padding-left:15px; + padding-top:1px; +} +ul.blockList, ul.blockListLast { + margin:10px 0 10px 0; + padding:0; +} +ul.blockList li.blockList, ul.blockListLast li.blockList { + list-style:none; + margin-bottom:15px; + line-height:1.4; +} +ul.blockList ul.blockList li.blockList, ul.blockList ul.blockListLast li.blockList { + padding:0px 20px 5px 10px; + border:1px solid #ededed; + background-color:#f8f8f8; +} +ul.blockList ul.blockList ul.blockList li.blockList, ul.blockList ul.blockList ul.blockListLast li.blockList { + padding:0 0 5px 8px; + background-color:#ffffff; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockList { + margin-left:0; + padding-left:0; + padding-bottom:15px; + border:none; +} +ul.blockList ul.blockList ul.blockList ul.blockList li.blockListLast { + list-style:none; + border-bottom:none; + padding-bottom:0; +} +table tr td dl, table tr td dl dt, table tr td dl dd { + margin-top:0; + margin-bottom:1px; +} +/* +Table styles +*/ +.overviewSummary, .memberSummary, .typeSummary, .useSummary, .constantsSummary, .deprecatedSummary { + width:100%; + border-left:1px solid #EEE; + border-right:1px solid #EEE; + border-bottom:1px solid #EEE; +} +.overviewSummary, .memberSummary { + padding:0px; +} +.overviewSummary caption, .memberSummary caption, .typeSummary caption, +.useSummary caption, .constantsSummary caption, .deprecatedSummary caption { + position:relative; + text-align:left; + background-repeat:no-repeat; + color:#253441; + font-weight:bold; + clear:none; + overflow:hidden; + padding:0px; + padding-top:10px; + padding-left:1px; + margin:0px; + white-space:pre; +} +.overviewSummary caption a:link, .memberSummary caption a:link, .typeSummary caption a:link, +.useSummary caption a:link, .constantsSummary caption a:link, .deprecatedSummary caption a:link, +.overviewSummary caption a:hover, .memberSummary caption a:hover, .typeSummary caption a:hover, +.useSummary caption a:hover, .constantsSummary caption a:hover, .deprecatedSummary caption a:hover, +.overviewSummary caption a:active, .memberSummary caption a:active, .typeSummary caption a:active, +.useSummary caption a:active, .constantsSummary caption a:active, .deprecatedSummary caption a:active, +.overviewSummary caption a:visited, .memberSummary caption a:visited, .typeSummary caption a:visited, +.useSummary caption a:visited, .constantsSummary caption a:visited, .deprecatedSummary caption a:visited { + color:#FFFFFF; +} +.overviewSummary caption span, .memberSummary caption span, .typeSummary caption span, +.useSummary caption span, .constantsSummary caption span, .deprecatedSummary caption span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + padding-bottom:7px; + display:inline-block; + float:left; + background-color:#F8981D; + border: none; + height:16px; +} +.memberSummary caption span.activeTableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#F8981D; + height:16px; +} +.memberSummary caption span.tableTab span { + white-space:nowrap; + padding-top:5px; + padding-left:12px; + padding-right:12px; + margin-right:3px; + display:inline-block; + float:left; + background-color:#4D7A97; + height:16px; +} +.memberSummary caption span.tableTab, .memberSummary caption span.activeTableTab { + padding-top:0px; + padding-left:0px; + padding-right:0px; + background-image:none; + float:none; + display:inline; +} +.overviewSummary .tabEnd, .memberSummary .tabEnd, .typeSummary .tabEnd, +.useSummary .tabEnd, .constantsSummary .tabEnd, .deprecatedSummary .tabEnd { + display:none; + width:5px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .activeTableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + float:left; + background-color:#F8981D; +} +.memberSummary .tableTab .tabEnd { + display:none; + width:5px; + margin-right:3px; + position:relative; + background-color:#4D7A97; + float:left; + +} +.overviewSummary td, .memberSummary td, .typeSummary td, +.useSummary td, .constantsSummary td, .deprecatedSummary td { + text-align:left; + padding:0px 0px 12px 10px; +} +th.colOne, th.colFirst, th.colLast, .useSummary th, .constantsSummary th, +td.colOne, td.colFirst, td.colLast, .useSummary td, .constantsSummary td{ + vertical-align:top; + padding-right:0px; + padding-top:8px; + padding-bottom:3px; +} +th.colFirst, th.colLast, th.colOne, .constantsSummary th { + background:#dee3e9; + text-align:left; + padding:8px 3px 3px 7px; +} +td.colFirst, th.colFirst { + white-space:nowrap; + font-size:13px; +} +td.colLast, th.colLast { + font-size:13px; +} +td.colOne, th.colOne { + font-size:13px; +} +.overviewSummary td.colFirst, .overviewSummary th.colFirst, +.useSummary td.colFirst, .useSummary th.colFirst, +.overviewSummary td.colOne, .overviewSummary th.colOne, +.memberSummary td.colFirst, .memberSummary th.colFirst, +.memberSummary td.colOne, .memberSummary th.colOne, +.typeSummary td.colFirst{ + width:25%; + vertical-align:top; +} +td.colOne a:link, td.colOne a:active, td.colOne a:visited, td.colOne a:hover, td.colFirst a:link, td.colFirst a:active, td.colFirst a:visited, td.colFirst a:hover, td.colLast a:link, td.colLast a:active, td.colLast a:visited, td.colLast a:hover, .constantValuesContainer td a:link, .constantValuesContainer td a:active, .constantValuesContainer td a:visited, .constantValuesContainer td a:hover { + font-weight:bold; +} +.tableSubHeadingColor { + background-color:#EEEEFF; +} +.altColor { + background-color:#FFFFFF; +} +.rowColor { + background-color:#EEEEEF; +} +/* +Content styles +*/ +.description pre { + margin-top:0; +} +.deprecatedContent { + margin:0; + padding:10px 0; +} +.docSummary { + padding:0; +} + +ul.blockList ul.blockList ul.blockList li.blockList h3 { + font-style:normal; +} + +div.block { + font-size:14px; + font-family:'DejaVu Serif', Georgia, "Times New Roman", Times, serif; +} + +td.colLast div { + padding-top:0px; +} + + +td.colLast a { + padding-bottom:3px; +} +/* +Formatting effect styles +*/ +.sourceLineNo { + color:green; + padding:0 30px 0 0; +} +h1.hidden { + visibility:hidden; + overflow:hidden; + font-size:10px; +} +.block { + display:block; + margin:3px 10px 2px 0px; + color:#474747; +} +.deprecatedLabel, .descfrmTypeLabel, .memberNameLabel, .memberNameLink, +.overrideSpecifyLabel, .packageHierarchyLabel, .paramLabel, .returnLabel, +.seeLabel, .simpleTagLabel, .throwsLabel, .typeNameLabel, .typeNameLink { + font-weight:bold; +} +.deprecationComment, .emphasizedPhrase, .interfaceName { + font-style:italic; +} + +div.block div.block span.deprecationComment, div.block div.block span.emphasizedPhrase, +div.block div.block span.interfaceName { + font-style:normal; +} + +div.contentContainer ul.blockList li.blockList h2{ + padding-bottom:0px; +} From 0bb6f79aad50f66e523be0c67987cbac2d25c590 Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 21 Jun 2023 17:14:58 +0800 Subject: [PATCH 073/105] =?UTF-8?q?feat:=20=E9=99=90=E6=B5=81=E7=AE=97?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- {core-java-rate-limiter => tool-java-rate-limiter}/README.md | 0 {core-java-rate-limiter => tool-java-rate-limiter}/pom.xml | 2 +- .../src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java | 0 .../java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java | 0 .../java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java | 0 .../java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java | 0 .../java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java | 0 .../src/main/resources/limiter.lua | 0 .../src/main/resources/limiter2.lua | 0 .../src/test/java/RedisLuaLimiterByIncr.java | 0 .../src/test/java/RedisLuaLimiterByZset.java | 0 11 files changed, 1 insertion(+), 1 deletion(-) rename {core-java-rate-limiter => tool-java-rate-limiter}/README.md (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/pom.xml (92%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/resources/limiter.lua (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/main/resources/limiter2.lua (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/test/java/RedisLuaLimiterByIncr.java (100%) rename {core-java-rate-limiter => tool-java-rate-limiter}/src/test/java/RedisLuaLimiterByZset.java (100%) diff --git a/core-java-rate-limiter/README.md b/tool-java-rate-limiter/README.md similarity index 100% rename from core-java-rate-limiter/README.md rename to tool-java-rate-limiter/README.md diff --git a/core-java-rate-limiter/pom.xml b/tool-java-rate-limiter/pom.xml similarity index 92% rename from core-java-rate-limiter/pom.xml rename to tool-java-rate-limiter/pom.xml index 9679d8d..9b92fde 100644 --- a/core-java-rate-limiter/pom.xml +++ b/tool-java-rate-limiter/pom.xml @@ -10,7 +10,7 @@ 4.0.0 com.wdbyte.rate.limiter - core-java-rate-limiter + tool-java-rate-limiter 17 diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java b/tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java similarity index 100% rename from core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java rename to tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterGuava.java diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java b/tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java similarity index 100% rename from core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java rename to tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSildingLog.java diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java b/tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java similarity index 100% rename from core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java rename to tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow.java diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java b/tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java similarity index 100% rename from core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java rename to tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSimpleWindow0.java diff --git a/core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java b/tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java similarity index 100% rename from core-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java rename to tool-java-rate-limiter/src/main/java/com/wdbyte/rate/limiter/RateLimiterSlidingWindow.java diff --git a/core-java-rate-limiter/src/main/resources/limiter.lua b/tool-java-rate-limiter/src/main/resources/limiter.lua similarity index 100% rename from core-java-rate-limiter/src/main/resources/limiter.lua rename to tool-java-rate-limiter/src/main/resources/limiter.lua diff --git a/core-java-rate-limiter/src/main/resources/limiter2.lua b/tool-java-rate-limiter/src/main/resources/limiter2.lua similarity index 100% rename from core-java-rate-limiter/src/main/resources/limiter2.lua rename to tool-java-rate-limiter/src/main/resources/limiter2.lua diff --git a/core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java b/tool-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java similarity index 100% rename from core-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java rename to tool-java-rate-limiter/src/test/java/RedisLuaLimiterByIncr.java diff --git a/core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java b/tool-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java similarity index 100% rename from core-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java rename to tool-java-rate-limiter/src/test/java/RedisLuaLimiterByZset.java From f8dda1be5ac4b44fafb27546de4521eb123fa91e Mon Sep 17 00:00:00 2001 From: niujinpeng Date: Wed, 21 Jun 2023 17:15:19 +0800 Subject: [PATCH 074/105] =?UTF-8?q?feat:=20=E9=99=90=E6=B5=81=E7=AE=97?= =?UTF-8?q?=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index eb573fb..413dd08 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 1.0.0-SNAPSHOT core-java-modules - core-java-rate-limiter + tool-java-rate-limiter tool-java-junit5-jupiter-starter leetcode tool-java-apache-httpclient From 42c006aa9ba6620823f355d857118495027558ac Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 12 Oct 2023 16:39:20 +0800 Subject: [PATCH 075/105] =?UTF-8?q?feat:=20=E9=87=8D=E6=9E=84=E7=9B=AE?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + core-java-modules/core-java-20/.gitignore | 38 ++++++++ core-java-modules/core-java-20/pom.xml | 20 ++++ .../src/main/java/JEP431Test.java | 63 +++++++++++++ .../src/{ => main/java}/JEP433SwitchTest.java | 0 .../java}/Jep429ScopedValueTest.java | 0 .../java}/Jep432RecordAndInstance.java | 0 .../src/{ => main/java}/RecordTest.java | 0 .../io/image/GeneratorTextImageMini.java | 89 ++++++++++++++++++ .../target/classes/bingdundun.jpeg | Bin 0 -> 22890 bytes core-java-modules/pom.xml | 7 +- pom.xml | 2 + tool-java-classloader/.gitignore | 38 ++++++++ tool-java-classloader/pom.xml | 20 ++++ tool-java-guava/.gitignore | 38 ++++++++ tool-java-guava/pom.xml | 20 ++++ tool-java-object-pool/pom.xml | 2 +- tool-java-protobuf/.gitignore | 38 ++++++++ 18 files changed, 372 insertions(+), 4 deletions(-) create mode 100644 core-java-modules/core-java-20/.gitignore create mode 100644 core-java-modules/core-java-20/pom.xml create mode 100644 core-java-modules/core-java-20/src/main/java/JEP431Test.java rename core-java-modules/core-java-20/src/{ => main/java}/JEP433SwitchTest.java (100%) rename core-java-modules/core-java-20/src/{ => main/java}/Jep429ScopedValueTest.java (100%) rename core-java-modules/core-java-20/src/{ => main/java}/Jep432RecordAndInstance.java (100%) rename core-java-modules/core-java-20/src/{ => main/java}/RecordTest.java (100%) create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImageMini.java create mode 100644 core-java-modules/core-java-io/target/classes/bingdundun.jpeg create mode 100644 tool-java-classloader/.gitignore create mode 100644 tool-java-classloader/pom.xml create mode 100644 tool-java-guava/.gitignore create mode 100644 tool-java-guava/pom.xml create mode 100644 tool-java-protobuf/.gitignore diff --git a/README.md b/README.md index 3bbe625..a39d68e 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ - [Java 日期时间 Date](https://www.wdbyte.com/java/date/) - [Java 异常处理](https://www.wdbyte.com/java/exception/) - [Java 枚举](https://www.wdbyte.com/java/enum/) +- [Java 注释](https://www.wdbyte.com/java/comment/) - [Java 集合框架](https://www.wdbyte.com/java/collection/) ## Java 进阶 diff --git a/core-java-modules/core-java-20/.gitignore b/core-java-modules/core-java-20/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/core-java-modules/core-java-20/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/core-java-modules/core-java-20/pom.xml b/core-java-modules/core-java-20/pom.xml new file mode 100644 index 0000000..0495012 --- /dev/null +++ b/core-java-modules/core-java-20/pom.xml @@ -0,0 +1,20 @@ + + + + core-java-modules + com.wdbyte.core-java-modules + 1.0.0-SNAPSHOT + + 4.0.0 + + com.wdbyte.jdk20 + core-java-20 + + + 20 + 20 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-20/src/main/java/JEP431Test.java b/core-java-modules/core-java-20/src/main/java/JEP431Test.java new file mode 100644 index 0000000..5096265 --- /dev/null +++ b/core-java-modules/core-java-20/src/main/java/JEP431Test.java @@ -0,0 +1,63 @@ +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.TreeSet; + +/** + * JDK 21 之前,顺序集合中操作体验不一致 + * @author niulang + * @date 2023/10/12ø + */ +public class JEP431Test { + public static void main(String[] args) { + // JDK 21 之前,顺序集合中操作体验不一致 + List listTemp = List.of(1, 2, 3, 4, 5); + + ArrayList list = new ArrayList(listTemp); + Deque deque = new ArrayDeque<>(listTemp); + LinkedHashSet linkedHashSet = new LinkedHashSet<>(listTemp); + TreeSet sortedSet = new TreeSet<>(listTemp); + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + for (int i = 1; i <= 5; i++) { + linkedHashMap.put(i, i); + } + + // 输出第一个元素 + System.out.println(list.get(0)); + System.out.println(deque.getFirst()); + System.out.println(linkedHashSet.iterator().next()); + System.out.println(sortedSet.first()); + //System.out.println(linkedHashMap.firstEntry());没办法 + System.out.println("-----------------------"); + + // 输出最后一个元素 + System.out.println(list.get(list.size()-1)); + System.out.println(deque.getLast()); + //System.out.println(linkedHashSet()); 没办法,只能遍历 + System.out.println(sortedSet.last()); + //System.out.println(linkedHashMap); 没办法 + System.out.println("-----------------------"); + + // 逆序遍历 + for (var it = list.listIterator(list.size()); it.hasPrevious();) { + var e = it.previous(); + System.out.print(e); + } + System.out.println(); + for (var it = deque.descendingIterator(); it.hasNext();) { + var e = it.next(); + System.out.print(e); + } + System.out.println(); + for (Integer i : sortedSet.descendingSet()) { + System.out.print(i); + } + System.out.println(); + + // sortedSet linkedHashMap 逆序输出很难操作 + + } +} diff --git a/core-java-modules/core-java-20/src/JEP433SwitchTest.java b/core-java-modules/core-java-20/src/main/java/JEP433SwitchTest.java similarity index 100% rename from core-java-modules/core-java-20/src/JEP433SwitchTest.java rename to core-java-modules/core-java-20/src/main/java/JEP433SwitchTest.java diff --git a/core-java-modules/core-java-20/src/Jep429ScopedValueTest.java b/core-java-modules/core-java-20/src/main/java/Jep429ScopedValueTest.java similarity index 100% rename from core-java-modules/core-java-20/src/Jep429ScopedValueTest.java rename to core-java-modules/core-java-20/src/main/java/Jep429ScopedValueTest.java diff --git a/core-java-modules/core-java-20/src/Jep432RecordAndInstance.java b/core-java-modules/core-java-20/src/main/java/Jep432RecordAndInstance.java similarity index 100% rename from core-java-modules/core-java-20/src/Jep432RecordAndInstance.java rename to core-java-modules/core-java-20/src/main/java/Jep432RecordAndInstance.java diff --git a/core-java-modules/core-java-20/src/RecordTest.java b/core-java-modules/core-java-20/src/main/java/RecordTest.java similarity index 100% rename from core-java-modules/core-java-20/src/RecordTest.java rename to core-java-modules/core-java-20/src/main/java/RecordTest.java diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImageMini.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImageMini.java new file mode 100644 index 0000000..4bad1d8 --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/image/GeneratorTextImageMini.java @@ -0,0 +1,89 @@ +package com.wdbyte.io.image; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +import javax.imageio.ImageIO; + +/** + *

+ * 根据图片生成字符图案 + * 1.图片大小缩放 + * 2.遍历图片像素点 + * 3.获取图片像素点亮度 + * 4.匹配字符 + * 5.输出图案 + * + * @website https://www.wdbyte.com + */ +public class GeneratorTextImageMini { + + public static void main(String[] args) throws Exception { + //BufferedImage image = resizeImage("/Users/darcy/Downloads/sanli.jpg", 120); + BufferedImage image = ImageIO.read(new File("/Users/darcy/Downloads/刘看山.jpg")); + printImage(image); + } + + /** + * 图片缩放 + * + * @param srcImagePath 图片路径 + * @param targetWidth 目标宽度 + * @return + * @throws IOException + */ + public static BufferedImage resizeImage(String srcImagePath, int targetWidth) throws IOException { + Image srcImage = ImageIO.read(new File(srcImagePath)); + int targetHeight = getTargetHeight(targetWidth, srcImage); + BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); + Graphics2D graphics2D = resizedImage.createGraphics(); + graphics2D.drawImage(srcImage, 0, 0, targetWidth, targetHeight, null); + graphics2D.dispose(); + return resizedImage; + } + + /** + * 根据指定宽度,计算等比例高度 + * + * @param targetWidth 目标宽度 + * @param srcImage 图片信息 + * @return + */ + private static int getTargetHeight(int targetWidth, Image srcImage) { + int targetHeight = srcImage.getHeight(null); + if (targetWidth < srcImage.getWidth(null)) { + targetHeight = Math.round((float)targetHeight / ((float)srcImage.getWidth(null) / (float)targetWidth)); + } + return targetHeight; + } + + /** + * 图片打印 + * + * @param image + * @throws IOException + */ + public static void printImage(BufferedImage image) throws IOException { + final char[] PIXEL_CHAR_ARRAY = {'W', '@', '#', '8', '&', '*', 'o', ':', '.', ' '}; + //final char[] PIXEL_CHAR_ARRAY = {',', '.', ' '}; + int width = image.getWidth(); + int height = image.getHeight(); + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int rgb = image.getRGB(j, i); + Color color = new Color(rgb); + int red = color.getRed(); + int green = color.getGreen(); + int blue = color.getBlue(); + // 一个用于计算RGB像素点灰度的公式 + Double grayscale = 0.2126 * red + 0.7152 * green + 0.0722 * blue; + double index = grayscale / (Math.ceil(255 / PIXEL_CHAR_ARRAY.length) + 0.5); + System.out.print(PIXEL_CHAR_ARRAY[(int)(Math.floor(index))]); + } + System.out.println(); + } + } + +} diff --git a/core-java-modules/core-java-io/target/classes/bingdundun.jpeg b/core-java-modules/core-java-io/target/classes/bingdundun.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8243a8a745e076f571efcbedfc1611ba50a7c9fe GIT binary patch literal 22890 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 - 1.8 - 1.8 + 21 + 21 core-java-performance core-java-8 core-java-9 + core-java-18 + core-java-20 core-java-performance-code core-java-io core-java-string - core-java-18 core-java-collect core-java-base core-java-os diff --git a/pom.xml b/pom.xml index 413dd08..0e2c7f6 100644 --- a/pom.xml +++ b/pom.xml @@ -19,6 +19,8 @@ tool-java-hotcode tool-java-protobuf tool-java-jcommander + tool-java-classloader + tool-java-guava parent-modules Parent for all java modules diff --git a/tool-java-classloader/.gitignore b/tool-java-classloader/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/tool-java-classloader/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/tool-java-classloader/pom.xml b/tool-java-classloader/pom.xml new file mode 100644 index 0000000..3789735 --- /dev/null +++ b/tool-java-classloader/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.wdbyte + parent-modules + 1.0.0-SNAPSHOT + + + tool-java-classloader + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/tool-java-guava/.gitignore b/tool-java-guava/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/tool-java-guava/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/tool-java-guava/pom.xml b/tool-java-guava/pom.xml new file mode 100644 index 0000000..64e6b94 --- /dev/null +++ b/tool-java-guava/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + com.wdbyte + parent-modules + 1.0.0-SNAPSHOT + + + tool-java-guava + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/tool-java-object-pool/pom.xml b/tool-java-object-pool/pom.xml index f8c4630..7044006 100644 --- a/tool-java-object-pool/pom.xml +++ b/tool-java-object-pool/pom.xml @@ -8,7 +8,7 @@ 1.0.0-SNAPSHOT 4.0.0 - objectpool + tool-java-object-pool 8 diff --git a/tool-java-protobuf/.gitignore b/tool-java-protobuf/.gitignore new file mode 100644 index 0000000..5ff6309 --- /dev/null +++ b/tool-java-protobuf/.gitignore @@ -0,0 +1,38 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file From dd7f782eb4f1595f71916a9681b370b2f58897ca Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 12 Oct 2023 16:40:25 +0800 Subject: [PATCH 076/105] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=E6=B3=A8?= =?UTF-8?q?=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-20/src/main/java/JEP431Test.java | 2 +- .../src/main/java/com/wdbyte/string/StringUtilsTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core-java-modules/core-java-20/src/main/java/JEP431Test.java b/core-java-modules/core-java-20/src/main/java/JEP431Test.java index 5096265..4a2edc0 100644 --- a/core-java-modules/core-java-20/src/main/java/JEP431Test.java +++ b/core-java-modules/core-java-20/src/main/java/JEP431Test.java @@ -8,7 +8,7 @@ /** * JDK 21 之前,顺序集合中操作体验不一致 - * @author niulang + * @author https://www.wdbyte.com * @date 2023/10/12ø */ public class JEP431Test { diff --git a/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java b/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java index fdaedda..51989b2 100644 --- a/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java +++ b/tool-java-apache-common/src/main/java/com/wdbyte/string/StringUtilsTest.java @@ -6,7 +6,7 @@ /** * apache common lang StringUtils test - * @author niulang + * @author https://www.wdbyte.com * @date 2022/11/01 */ public class StringUtilsTest { From 06872dc9628ffab727e659fb9affdbc166931d5f Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 16 Oct 2023 10:57:47 +0800 Subject: [PATCH 077/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Java=2021?= =?UTF-8?q?=20=E6=96=B0=E7=89=B9=E6=80=A7=E7=A4=BA=E4=BE=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/JEP431Test.java | 2 - core-java-modules/core-java-21/README.md | 1 + core-java-modules/core-java-21/pom.xml | 21 ++++++ .../src/main/java/Jep430StringTemplate.java | 67 +++++++++++++++++++ .../main/java/Jep431SequencedCollection.java | 60 +++++++++++++++++ .../src/main/java/Jep440Record.java | 29 ++++++++ .../src/main/java/Jep441SwitchPatten.java | 33 +++++++++ .../src/main/java/Jep443Unname.java | 19 ++++++ .../src/main/java/Jep444VirtualThread.java | 48 +++++++++++++ .../src/main/java/Jep445HelloJava.java | 5 ++ core-java-modules/pom.xml | 1 + 11 files changed, 284 insertions(+), 2 deletions(-) create mode 100644 core-java-modules/core-java-21/README.md create mode 100644 core-java-modules/core-java-21/pom.xml create mode 100644 core-java-modules/core-java-21/src/main/java/Jep430StringTemplate.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep431SequencedCollection.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep440Record.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep441SwitchPatten.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep443Unname.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep444VirtualThread.java create mode 100644 core-java-modules/core-java-21/src/main/java/Jep445HelloJava.java diff --git a/core-java-modules/core-java-20/src/main/java/JEP431Test.java b/core-java-modules/core-java-20/src/main/java/JEP431Test.java index 4a2edc0..22b80f8 100644 --- a/core-java-modules/core-java-20/src/main/java/JEP431Test.java +++ b/core-java-modules/core-java-20/src/main/java/JEP431Test.java @@ -9,7 +9,6 @@ /** * JDK 21 之前,顺序集合中操作体验不一致 * @author https://www.wdbyte.com - * @date 2023/10/12ø */ public class JEP431Test { public static void main(String[] args) { @@ -58,6 +57,5 @@ public static void main(String[] args) { System.out.println(); // sortedSet linkedHashMap 逆序输出很难操作 - } } diff --git a/core-java-modules/core-java-21/README.md b/core-java-modules/core-java-21/README.md new file mode 100644 index 0000000..5fd1f86 --- /dev/null +++ b/core-java-modules/core-java-21/README.md @@ -0,0 +1 @@ +## Java 21 新特性示例 diff --git a/core-java-modules/core-java-21/pom.xml b/core-java-modules/core-java-21/pom.xml new file mode 100644 index 0000000..c7fdc19 --- /dev/null +++ b/core-java-modules/core-java-21/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + com.wdbyte.core-java-modules + core-java-modules + 1.0.0-SNAPSHOT + + + com.wdbyte.jdk21 + core-java-21 + + + 21 + 21 + UTF-8 + + + \ No newline at end of file diff --git a/core-java-modules/core-java-21/src/main/java/Jep430StringTemplate.java b/core-java-modules/core-java-21/src/main/java/Jep430StringTemplate.java new file mode 100644 index 0000000..51a7100 --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep430StringTemplate.java @@ -0,0 +1,67 @@ +import java.time.LocalDate; +import java.util.FormatProcessor; +import java.util.Locale; + +/** + * 字符串模版 + * + * @author https://www.wdbyte.com + * @date 2023/10/13 + */ +public class Jep430StringTemplate { + + public static void main(String[] args) { + int x = 20; + int y = 3; + String s = x + " + " + y + " = " + (x + y); + System.out.println(s); + + String sb = new StringBuilder().append(x).append(" + ").append(y).append(" = ").append(x + y).toString(); + System.out.println(sb); + + + String sFormat = String.format("%d + %d = %d", x, y, x + y); + System.out.println(sFormat); + + System.out.println("--------------------"); + + // JDK 21 使用字符串模版 STR 进行插值 + String sTemplate = StringTemplate.STR."\{x} + \{y} = \{x+y}"; + System.out.println(sTemplate); + + // 字符串模版也可以先定义模版,再处理插值 + StringTemplate st = StringTemplate.RAW."\{x} + \{y} = \{x+y}"; + String sTemplate2 = StringTemplate.STR.process(st); + System.out.println(sTemplate2); + + System.out.println("--------------------"); + LocalDate now = LocalDate.now(); + String nowStr = StringTemplate.STR."现在是 \{now.getYear()} 年 \{now.getMonthValue()} 月 \{now.getDayOfMonth()} 号"; + System.out.println(nowStr); + + // 字符串模版读取数组,字符串模版也可以嵌套 + String[] infoArr = { "Hello", "Java 21", "https://www.wdbyte.com" }; + String sArray = StringTemplate.STR."\{infoArr[0]}, \{STR."\{infoArr[1]}, \{infoArr[2]}"}"; + System.out.println(sArray); + + // 字符串模版也可以结合多行文本 + String name = "https://www.wdbyte.com"; + String address = "程序猿阿朗"; + String json = StringTemplate.STR.""" + { + "name": "\{name}", + "address": "\{address}" + } + """; + System.out.println(json); + System.out.println("--------------------"); + + } +} + + +record Rectangle(String name, double width, double height) { + double area() { + return width * height; + } +} diff --git a/core-java-modules/core-java-21/src/main/java/Jep431SequencedCollection.java b/core-java-modules/core-java-21/src/main/java/Jep431SequencedCollection.java new file mode 100644 index 0000000..9034ea1 --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep431SequencedCollection.java @@ -0,0 +1,60 @@ +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.SequencedCollection; +import java.util.TreeSet; +import java.util.function.Consumer; + +/** + * @author https://www.wdbyte.com + * @date 2023/10/12 + */ +public class Jep431SequencedCollection { + public static void main(String[] args) { + // JDK 21 之后,为所有元素插入有序集合提供了一致的操作 API + List listTemp = List.of(1, 2, 3, 4, 5); + + ArrayList list = new ArrayList(listTemp); + Deque deque = new ArrayDeque<>(listTemp); + LinkedHashSet linkedHashSet = new LinkedHashSet<>(listTemp); + TreeSet sortedSet = new TreeSet<>(listTemp); + LinkedHashMap linkedHashMap = new LinkedHashMap<>(); + for (int i = 1; i <= 5; i++) { + linkedHashMap.put(i, i); + } + + // 输出第一个元素 + System.out.println(list.getFirst()); + System.out.println(deque.getFirst()); + System.out.println(linkedHashSet.getFirst()); + System.out.println(sortedSet.getFirst()); + System.out.println(linkedHashMap.firstEntry()); + System.out.println("-----------------------"); + + // 输出最后一个元素 + System.out.println(list.getLast()); + System.out.println(list.getLast()); + System.out.println(deque.getLast()); + System.out.println(sortedSet.getLast()); + System.out.println(linkedHashMap.lastEntry()); + System.out.println("-----------------------"); + + // 逆序遍历 + Consumer printFn = s -> { + // reversed 逆序元素 + s.reversed().forEach(System.out::print); + System.out.println(); + }; + printFn.accept(list); + printFn.accept(deque); + printFn.accept(linkedHashSet); + printFn.accept(sortedSet); + // 有序 map 接口是 SequencedMap,上面的 consume类型不适用 + linkedHashMap.reversed().forEach((k, v) -> { + System.out.print(k); + }); + } +} diff --git a/core-java-modules/core-java-21/src/main/java/Jep440Record.java b/core-java-modules/core-java-21/src/main/java/Jep440Record.java new file mode 100644 index 0000000..51e59b9 --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep440Record.java @@ -0,0 +1,29 @@ +/** + * @author https://www.wdbyte.com + * @date 2023/10/13 + */ +public class Jep440Record { + public static void main(String[] args) { + + Object obj = "Hello www.wdbyte.com"; + if (obj instanceof String s) { + System.out.println(s); + } + + Dog dog = new Dog("Husky", 1); + if (dog instanceof Dog(String name, int age)) { + String res = StringTemplate.STR."name:\{name} age:\{age}"; + System.out.println(res); + } + Object myDog = new MyDog(dog, Color.BLACK); + if (myDog instanceof MyDog(Dog(String name,int age),Color color)){ + String res = StringTemplate.STR."name:\{name} age:\{age} color:\{color}"; + System.out.println(res); + } + } +} + +record Dog(String name, int age) {} +enum Color{WHITE,GREY,BLACK}; +record MyDog(Dog dog,Color color){}; + diff --git a/core-java-modules/core-java-21/src/main/java/Jep441SwitchPatten.java b/core-java-modules/core-java-21/src/main/java/Jep441SwitchPatten.java new file mode 100644 index 0000000..c1d6869 --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep441SwitchPatten.java @@ -0,0 +1,33 @@ +/** + * @author https://www.wdbyte.com + * @date 2023/10/13 + */ +public class Jep441SwitchPatten { + + public static void main(String[] args) { + String r1 = formatterPatternSwitch(Integer.valueOf(1)); + String r2 = formatterPatternSwitch(new String("www.wdbyte.com")); + String r3 = formatterPatternSwitch(Double.valueOf(3.14D)); + System.out.println(r1); + System.out.println(r2); + System.out.println(r3); + } + + static String formatterPatternSwitch(Object obj) { + return switch (obj) { + case Integer i -> String.format("int %d", i); + case Long l -> String.format("long %d", l); + case Double d -> String.format("double %f", d); + case String s -> String.format("String %s", s); + default -> obj.toString(); + }; + } + + static void testFooBarNew(String s) { + switch (s) { + case null -> System.out.println("Oops"); + case "Foo", "Bar" -> System.out.println("Great"); + default -> System.out.println("Ok"); + } + } +} diff --git a/core-java-modules/core-java-21/src/main/java/Jep443Unname.java b/core-java-modules/core-java-21/src/main/java/Jep443Unname.java new file mode 100644 index 0000000..5a4c356 --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep443Unname.java @@ -0,0 +1,19 @@ +import java.util.List; + +/** + * @author https://www.wdbyte.com + * @date 2023/10/15 + */ +public class Jep443Unname { + + public static void main(String[] args) { + String _ = "123213"; + Integer _ = 123; + List list = List.of(1, 2, 3, 4, 5, 6, 7, 8); + int count = 0; + for (Integer _ : list) { + count++; + } + System.out.println(count); + } +} diff --git a/core-java-modules/core-java-21/src/main/java/Jep444VirtualThread.java b/core-java-modules/core-java-21/src/main/java/Jep444VirtualThread.java new file mode 100644 index 0000000..52c565c --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep444VirtualThread.java @@ -0,0 +1,48 @@ +import java.time.Duration; +import java.util.concurrent.Executors; +import java.util.stream.IntStream; + +/** + * 虚拟线程 + * + * @author https://www.wdbyte.com + * @date 2023/10/10 + */ +public class Jep444VirtualThread { + public static void main(String[] args) throws InterruptedException { + // 创建并提交执行虚拟线程 + long start = System.currentTimeMillis(); + try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { + IntStream.range(0, 10_000).forEach(i -> { + executor.submit(() -> { + Thread.sleep(Duration.ofSeconds(1)); + return i; + }); + }); + } + System.out.println("time:" + (System.currentTimeMillis() - start) + "ms"); + + + // 创建一个虚拟线程指定虚拟线程名称 + Thread thread1 = Thread.ofVirtual().name("v-thread").unstarted(() -> { + String threadName = Thread.currentThread().getName(); + System.out.println(String.format("[%s] Hello Virtual Thread", threadName)); + }); + thread1.start(); + System.out.println(thread1.isVirtual()); + + //创建一个线程,启动为虚拟线程 + Thread thread2 = new Thread(() -> { + String threadName = Thread.currentThread().getName(); + System.out.println(String.format("[%s] Hello Virtual Thread 2", threadName)); + }); + + Thread.startVirtualThread(thread2); + + // 判断一个线程是否是虚拟线程 + System.out.println(thread1.isVirtual()); + System.out.println(thread2.isVirtual()); + + Thread.sleep(1000); + } +} diff --git a/core-java-modules/core-java-21/src/main/java/Jep445HelloJava.java b/core-java-modules/core-java-21/src/main/java/Jep445HelloJava.java new file mode 100644 index 0000000..9f5ba6b --- /dev/null +++ b/core-java-modules/core-java-21/src/main/java/Jep445HelloJava.java @@ -0,0 +1,5 @@ + +void main(){ + System.out.println("Hello, Java 21!"); +} + diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index 71f10f2..ed84483 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -24,6 +24,7 @@ core-java-9 core-java-18 core-java-20 + core-java-21 core-java-performance-code core-java-io core-java-string From 4f94745e2929e0bab159e06c6727dc20523165a3 Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 16 Oct 2023 15:37:39 +0800 Subject: [PATCH 078/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Java=2021?= =?UTF-8?q?=20=E6=96=B0=E7=89=B9=E6=80=A7=E7=A4=BA=E4=BE=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a39d68e..9a6f199 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,8 @@ ## ☕ Java 新特性 Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错,何况有些新特性是**真香**。 + +-[Java 20 新功能介绍 (LTS)]https://www.wdbyte.com/java/java-21/ - [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) - [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) - [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) From e1e7ce75ec13470e39fd1cdc72326eef7eaae3d8 Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 16 Oct 2023 16:41:17 +0800 Subject: [PATCH 079/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Java=2021?= =?UTF-8?q?=20=E6=96=B0=E7=89=B9=E6=80=A7=E7=A4=BA=E4=BE=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a6f199..18c04d2 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错,何况有些新特性是**真香**。 --[Java 20 新功能介绍 (LTS)]https://www.wdbyte.com/java/java-21/ +- [Java 21 新功能介绍 (LTS)](https://www.wdbyte.com/java/java-21/) - [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) - [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) - [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) From 5fd51348a135c6566c65311608336d818f58c09d Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 16 Oct 2023 16:41:48 +0800 Subject: [PATCH 080/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20Java=2021?= =?UTF-8?q?=20=E6=96=B0=E7=89=B9=E6=80=A7=E7=A4=BA=E4=BE=8B=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 18c04d2..3b4f6fc 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) - [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) - [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) -- [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) +- [Java 17 新功能介绍 (LTS)](https://www.wdbyte.com/java/java-17/) - [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) - [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/) - [Java 14 新特性讲解](https://www.wdbyte.com/java/java-14/) From ac763c61ccc533f85565b7aef3b0c9c07ad0b722 Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 19 Oct 2023 21:34:15 +0800 Subject: [PATCH 081/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20java=20lis?= =?UTF-8?q?t=20=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-base/README.md | 25 +++++++ .../com/wdbyte/collection/ArrayListTest.java | 63 +++++++++++++++++ .../com/wdbyte/collection/ArrayListTest2.java | 49 +++++++++++++ .../com/wdbyte/collection/ArrayListTest3.java | 54 ++++++++++++++ .../com/wdbyte/collection/ArrayListTest4.java | 70 +++++++++++++++++++ .../src/main/java/com/wdbyte/enum2/Shape.java | 12 ++++ .../main/java/com/wdbyte/enum2/Weekday.java | 11 +++ .../resources/META-INF/maven/archetype.xml | 9 +++ 8 files changed, 293 insertions(+) create mode 100644 core-java-modules/core-java-base/README.md create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Shape.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Weekday.java create mode 100644 core-java-modules/core-java-base/src/main/resources/META-INF/maven/archetype.xml diff --git a/core-java-modules/core-java-base/README.md b/core-java-modules/core-java-base/README.md new file mode 100644 index 0000000..0071ce6 --- /dev/null +++ b/core-java-modules/core-java-base/README.md @@ -0,0 +1,25 @@ +## core-java-base +当前模块包含 Java 核心代码 + +### 相关文章 + +- [JDK、JRE、JVM 的区别](/java/jdk-jre-jvm/) +- [Java 数据类型](/java/data-type/) +- [Java 流程控制](/java/flow-control/) +- [Java String 字符串](/java/java-string/) +- [Java Array 数组](/java/java-array/) +- [Java 多维数组](/java/java-array-mul/) +- [Java StringBuilder](/java/java-stringbuilder/) +- [Java Scanner](/java/scanner/) +- [Java 继承](/java/extends/) +- [Java 接口](/java/interface/) +- [Java 抽象类](/java/abstract/) +- [抽象类和接口的区别](https://www.wdbyte.com/java/abs-interface/) +- [Java 多态](/java/polymorphism/) +- [Java Scanner](/java/scanner/) +- [Java 日期时间Date](/java/date/) +- [Java 异常处理](/java/exception/) +- [Java 枚举](https://www.wdbyte.com/java/enum/) +- [Java 注释](*https://www.wdbyte.com/java/comment/*) +- [Java 集合框架](https://www.wdbyte.com/java/collection/) +- [Java 中使用 List ](https://www.wdbyte.com/java/list/) \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java new file mode 100644 index 0000000..54032e0 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java @@ -0,0 +1,63 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author niulang + * @date 2023/10/19 + */ +public class ArrayListTest { + + public static void main(String[] args) { + //String[] arr = new String[]{"w","d","b","y","t","e"}; + ////arr[7] = "f"; + // + //List list = new ArrayList(); + //list.add("a"); + //list.add("b"); + //list.add("c"); + //list.add("d"); + // + //System.out.println(list.get(2)); + //list.remove(2); + //System.out.println(list.get(2)); + + //List list2 = List.of("www", "wdbyte", "com"); + //list2.add("a"); + //System.out.println(list2); + + List list = new ArrayList(); + list.add("b"); + list.add("c"); + list.add("a"); + list.add("d"); + + System.out.println(list); + + list = list.stream().sorted().collect(Collectors.toList()); + System.out.println(list); + + // 排序时指定降序还是升序:Comparator.reverseOrder() 降序 + list = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()); + System.out.println(list); + + System.out.println("----------"); + + Collections.sort(list); + System.out.println(list); + // 降序 + Collections.sort(list,Comparator.reverseOrder()); + System.out.println(list); + + list.sort(Comparator.comparing(Function.identity())); + System.out.println(list); + + + + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java new file mode 100644 index 0000000..357e0f2 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java @@ -0,0 +1,49 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Vector; + +/** + * @author niulang + * @date 2023/10/19 + */ +public class ArrayListTest2 { + + public static void main(String[] args) { + List list = new ArrayList(); + list.add("a"); + list.add("b"); + System.out.println("当前集合:" + list); + System.out.println("获取第一个元素:" + list.get(0)); + list.set(0, "x"); + System.out.println("修改第一个元素为 x:" + list); + list.remove(1); + System.out.println("移除第2个元素后剩余:" + list); + System.out.println("判断元素 x 是否存在:" + list.contains("x")); + System.out.println("判断元素 a 是否存在:" + list.contains("a")); + System.out.println("当前 list 大小:" + list.size()); + + list.add("y"); + list.add("z"); + System.out.println("添加两个元素:" + list); + + System.out.println("遍历元素:"); + for (String string : list) { + System.out.print(string); + } + for (int i = 0; i < list.size(); i++) { + System.out.print(list.get(i)); + } + list.forEach(s -> { + System.out.println(s); + }); + + Iterator iterator = list.iterator(); + while (iterator.hasNext()){ + System.out.println(iterator.next()); + } + + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java new file mode 100644 index 0000000..02b189f --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java @@ -0,0 +1,54 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; + +/** + * @author niulang + * @date 2023/10/19 + */ +public class ArrayListTest3 { + + public static void main(String[] args) { + List list = new ArrayList(); + list.add("a"); + list.add("b"); + + String[] array = list.toArray(new String[0]); + System.out.println(Arrays.toString(array)); + + String[] array2 = list.stream().toArray(String[]::new); + System.out.println(Arrays.toString(array2)); + + // 数组 -> List + // 方式1 + List list0 = Lists.newArrayList(array); + list0.add("e"); + System.out.println(list0); + + // 方式2 + List list1 = Arrays.stream(array).toList(); + // list1.add("e"); 报错,不能修改 + System.out.println(list1); + + // 方式3 + List list2 = Arrays.asList(array); + //list2.add("e"); 报错,不能修改 + System.out.println(list2); + + // 方式4 + List list3 = new ArrayList<>(Arrays.asList(array)); + list3.add("e"); + System.out.println(list3); + + // 方式5 + List list4 = Arrays.stream(array).collect(Collectors.toList()); + list4.add("e"); + System.out.println(list4); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java new file mode 100644 index 0000000..98200cf --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java @@ -0,0 +1,70 @@ +package com.wdbyte.collection; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.collect.Lists; + +/** + * @author niulang + * @date 2023/10/19 + */ +public class ArrayListTest4 { + + public static void main(String[] args) { + List list = new ArrayList(); + list.add(new Dog("大黄", 1)); + list.add(new Dog("小黑", 2)); + + Map dogMap = list.stream() + .collect(Collectors.toMap(Dog::getName, Function.identity(), (o, n) -> o)); + System.out.println(dogMap.get("大黄")); + + Map dogMap2 = new HashMap<>(); + for (Dog dog : list) { + dogMap2.put(dog.getName(), dog); + } + System.out.println(dogMap.get("大黄")); + + } + +} + +class Dog { + String name; + Integer age; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Dog(String name, Integer age) { + this.name = name; + this.age = age; + } + + @Override + public String toString() { + return "Dog{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Shape.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Shape.java new file mode 100644 index 0000000..8bc2d2d --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Shape.java @@ -0,0 +1,12 @@ +package com.wdbyte.enum2; + +/** + * 形状接口 + */ +public interface Shape { + /** + * 计算面积 + * @return + */ + double getArea(); +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Weekday.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Weekday.java new file mode 100644 index 0000000..e8aa799 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/Weekday.java @@ -0,0 +1,11 @@ +package com.wdbyte.enum2; + +public enum Weekday { + MONDAY, + TUESDAY, + WEDNESDAY, + THURSDAY, + FRIDAY, + SATURDAY, + SUNDAY +} \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/resources/META-INF/maven/archetype.xml b/core-java-modules/core-java-base/src/main/resources/META-INF/maven/archetype.xml new file mode 100644 index 0000000..5cfc7cc --- /dev/null +++ b/core-java-modules/core-java-base/src/main/resources/META-INF/maven/archetype.xml @@ -0,0 +1,9 @@ + + core-java-base + + src/main/java/App.java + + + src/test/java/AppTest.java + + From d3e0d823d2e53edab6d0d2c7ac68f31ed7c4ccc1 Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 19 Oct 2023 21:36:34 +0800 Subject: [PATCH 082/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=20java=20lis?= =?UTF-8?q?t=20=E7=9B=B8=E5=85=B3=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + core-java-modules/README.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/README.md b/README.md index 3b4f6fc..868440e 100644 --- a/README.md +++ b/README.md @@ -53,6 +53,7 @@ - [Java 枚举](https://www.wdbyte.com/java/enum/) - [Java 注释](https://www.wdbyte.com/java/comment/) - [Java 集合框架](https://www.wdbyte.com/java/collection/) +- [Java 中使用 List](https://www.wdbyte.com/java/list/) ## Java 进阶 - [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) diff --git a/core-java-modules/README.md b/core-java-modules/README.md index 6a78bae..8f83756 100644 --- a/core-java-modules/README.md +++ b/core-java-modules/README.md @@ -2,6 +2,11 @@ 当前模块包含 Java 核心代码 ### 相关文章 + +- [Java 21 新功能介绍](https://www.wdbyte.com/java/java-21/) +- [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) +- [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) +- [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) - [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) - [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) - [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/) From f1f12dfd2f469da152a64072f7fcc3d047d0671f Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 8 Nov 2023 14:05:50 +0800 Subject: [PATCH 083/105] =?UTF-8?q?feat:=20=20[Java=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=92=8C=E5=86=99=E5=85=A5=E6=96=87=E4=BB=B6](https://www.wdby?= =?UTF-8?q?te.com/java/io/file-create-write/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/wdbyte/collection/ArrayListTest4.java | 13 +++ core-java-modules/core-java-io/README.md | 2 +- core-java-modules/core-java-io/pom.xml | 4 +- .../io/file/FileCreateAndWriteDemo.java | 108 ++++++++++++++++++ core-java-modules/pom.xml | 9 ++ 5 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java index 98200cf..d49f5b2 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -31,6 +32,18 @@ public static void main(String[] args) { } System.out.println(dogMap.get("大黄")); + List arrayList = new ArrayList(); + arrayList.add("a"); + arrayList.add("b"); + + Iterator iterator = arrayList.iterator(); + while (iterator.hasNext()) { + String next = iterator.next(); + if ("a".equals(next)) { + iterator.remove(); + } + } + System.out.println(arrayList); } } diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md index 5d1fb00..3f91046 100644 --- a/core-java-modules/core-java-io/README.md +++ b/core-java-modules/core-java-io/README.md @@ -2,5 +2,5 @@ 当前模块包含 IO 相关代码 ### 相关文章 - +- [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml index 2fb5dba..88e5007 100644 --- a/core-java-modules/core-java-io/pom.xml +++ b/core-java-modules/core-java-io/pom.xml @@ -14,8 +14,8 @@ jar - 1.8 - 1.8 + 21 + 21 diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java new file mode 100644 index 0000000..4f4dddd --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java @@ -0,0 +1,108 @@ +package com.wdbyte.io.file; + +import java.io.BufferedWriter; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2023/11/06 + */ +public class FileCreateAndWriteDemo { + + @Test + public void test0() throws IOException { + String content = "www.wdbyte.com,java 7"; + String filePath = "/Users/darcy/wdbyte/test0.txt"; + try (FileWriter fw = new FileWriter(filePath); + BufferedWriter bw = new BufferedWriter(fw)) { + bw.write(content); + bw.newLine(); + } + + // 追加模式 + try (FileWriter fw = new FileWriter(filePath, true); + BufferedWriter bw = new BufferedWriter(fw)) { + bw.write(content); + bw.newLine(); + } + } + + /** + * JDK 7 + * + * @throws IOException + */ + @Test + public void test1() throws IOException { + String content = "www.wdbyte.com, java 7"; + Path path = Paths.get("/Users/darcy/wdbyte/test1.txt"); + // string -> bytes + Files.write(path, content.getBytes(StandardCharsets.UTF_8)); + } + + /** + * JDK 7 追加 + * @throws IOException + */ + @Test + public void test2() throws IOException { + String content = "www.wdbyte.com,java 7" + System.lineSeparator(); + Path path = Paths.get("/Users/darcy/wdbyte/test2.txt"); + Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } + + /** + * JDK 7 写入 List + * + * @throws IOException + */ + @Test + public void test3() throws IOException { + List dataList = Arrays.asList("www", "wdbyte", "com", "java 7"); + Path path = Paths.get("/Users/darcy/wdbyte/test3.txt"); + Files.write(path, dataList); + } + + @Test + public void test4() throws IOException { + String content = "www.wdbyte.com,java 7"; + Path path = Paths.get("/Users/darcy/wdbyte/test4.txt"); + // 默认 utf_8 + try (BufferedWriter bw = Files.newBufferedWriter(path)) { + bw.write(content); + bw.newLine(); + } + // 追加模式 + try (BufferedWriter bw = Files.newBufferedWriter(path, + StandardOpenOption.CREATE, StandardOpenOption.APPEND)) { + bw.write(content); + bw.newLine(); + } + } + + /** + * JDK 11 + * + * @throws IOException + */ + @Test + public void test5() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/test5.txt"); + Files.writeString(path, "www.wdbyte.com,java 11"); + } + + +} diff --git a/core-java-modules/pom.xml b/core-java-modules/pom.xml index ed84483..b3a49a2 100644 --- a/core-java-modules/pom.xml +++ b/core-java-modules/pom.xml @@ -33,4 +33,13 @@ core-java-os + + + org.junit.jupiter + junit-jupiter + 5.9.1 + + + + \ No newline at end of file From b4f265b9090d09dd234ae003e2ec9f9e8158cb5b Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 8 Nov 2023 14:06:23 +0800 Subject: [PATCH 084/105] =?UTF-8?q?feat:=20=20[Java=20=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=92=8C=E5=86=99=E5=85=A5=E6=96=87=E4=BB=B6](https://www.wdby?= =?UTF-8?q?te.com/java/io/file-create-write/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 868440e..0db6717 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ - [Java 集合框架](https://www.wdbyte.com/java/collection/) - [Java 中使用 List](https://www.wdbyte.com/java/list/) +## Java I/O 教程 +- [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) + ## Java 进阶 - [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) From bcc1c8b5c7e33726ac0a936b6473b76028ae2e08 Mon Sep 17 00:00:00 2001 From: niumoo Date: Thu, 9 Nov 2023 14:36:34 +0800 Subject: [PATCH 085/105] =?UTF-8?q?feat:=20-=20[Java=20=E8=AF=BB=E5=8F=96?= =?UTF-8?q?=E6=96=87=E4=BB=B6](https://www.wdbyte.com/java/io/file-read/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + core-java-modules/core-java-io/README.md | 1 + .../java/com/wdbyte/io/file/FileReadDemo.java | 168 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java diff --git a/README.md b/README.md index 0db6717..fbbfa9a 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ ## Java I/O 教程 - [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) +- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/) ## Java 进阶 - [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md index 3f91046..8ebf834 100644 --- a/core-java-modules/core-java-io/README.md +++ b/core-java-modules/core-java-io/README.md @@ -2,5 +2,6 @@ 当前模块包含 IO 相关代码 ### 相关文章 +- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/) - [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java new file mode 100644 index 0000000..041a6b9 --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java @@ -0,0 +1,168 @@ +package com.wdbyte.io.file; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Scanner; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2023/11/08 + */ +public class FileReadDemo { + + public static void main(String[] args) { + Path path = Paths.get("/Users/darcy/wdbyte"); + System.out.println(Files.isDirectory(path)); + System.out.println(path.toFile().isFile()); + System.out.println(path.toFile().isDirectory()); + } + + /** + * java 8 + * + * @throws IOException + */ + @Test + public void test1() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/log.txt"); + List lines = Files.readAllLines(path, StandardCharsets.UTF_8); + for (String line : lines) { + System.out.println(line); + } + } + + /** + * java 8 + * + * @throws IOException + */ + @Test + public void test2() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/log.txt"); + byte[] bytes = Files.readAllBytes(path); + String content = new String(bytes, StandardCharsets.UTF_8); + System.out.println(content); + } + + /** + * java 8 + * + * @throws IOException + */ + @Test + public void test3() throws IOException, InterruptedException { + Path path = Paths.get("/Users/darcy/wdbyte/log.txt"); + try (Stream stream = Files.lines(path);) { + stream.forEach(System.out::println); + } + try (Stream stream = Files.lines(path);) { + stream.parallel().forEachOrdered(System.out::println); + } + } + + /** + * java 11 + * 要求:文件小于2G + * + * @throws IOException + */ + @Test + public void test4() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/log.txt"); + String content = Files.readString(path, StandardCharsets.UTF_8); + System.out.println(content); + } + + @Test + public void test5() { + File file = new File("/Users/darcy/wdbyte/log.txt"); + String line; + // 默认读取缓冲区大小 8K + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // 定义缓冲区大小为 128 K + int buffer = 128 * 1024; + try (BufferedReader br = new BufferedReader(new FileReader(file), buffer)) { + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * java 8 + * + * @throws IOException + */ + @Test + public void test6() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/log.txt"); + String line; + try (BufferedReader br = Files.newBufferedReader(path, StandardCharsets.UTF_8);) { + while ((line = br.readLine()) != null) { + System.out.println(line); + } + } + } + + /** + * 自古以来 + */ + @Test + public void test7() { + File file = new File("/Users/darcy/wdbyte/log.txt"); + try (Scanner sc = new Scanner(new FileReader(file))) { + while (sc.hasNextLine()) { + String line = sc.nextLine(); + System.out.println(line); + } + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + /** + * 自古以来 + */ + @Test + public void test8() { + File file = new File("/Users/darcy/wdbyte/log.txt"); + try (FileInputStream fis = new FileInputStream(file); + BufferedInputStream bis = new BufferedInputStream(fis);) { + + StringBuilder content = new StringBuilder(); + int data; + while ((data = bis.read()) != -1) { + content.append((char)data); + } + System.out.println(content.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + } +} From 09d3bde74d730176135b3ce3a5b85a0f0988b875 Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 18 Dec 2023 21:53:12 +0800 Subject: [PATCH 086/105] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E4=BB=B6IO=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core-java-modules/core-java-io/pom.xml | 10 +- .../com/wdbyte/io/file/FileAppendDemo.java | 179 ++++++++++++++++++ .../java/com/wdbyte/io/file/FileDelete.java | 73 +++++++ .../java/com/wdbyte/io/file/FileReadDemo.java | 10 + 4 files changed, 269 insertions(+), 3 deletions(-) create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java create mode 100644 core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java diff --git a/core-java-modules/core-java-io/pom.xml b/core-java-modules/core-java-io/pom.xml index 88e5007..30c75c1 100644 --- a/core-java-modules/core-java-io/pom.xml +++ b/core-java-modules/core-java-io/pom.xml @@ -22,10 +22,14 @@ com.google.guava guava - 31.0.1-jre - jar - provided + 32.1.3-jre + + commons-io + commons-io + 2.7 + + diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java new file mode 100644 index 0000000..6d43afb --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java @@ -0,0 +1,179 @@ +package com.wdbyte.io.file; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; +import java.util.List; + +import com.google.common.base.Charsets; +import com.google.common.io.FileWriteMode; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2023/12/12 + */ +public class FileAppendDemo { + + @Test + public void appendLine1() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/test1.txt"); + // 写入一行数据 + Files.write(path, "line append1".getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, StandardOpenOption.APPEND); + // 写入一个换行符 + Files.write(path, System.lineSeparator().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.APPEND); + } + + @Test + public void appendLine2() throws IOException { + String path = "/Users/darcy/wdbyte/test1.txt"; + try (FileWriter fileWriter = new FileWriter(path, true)) { + fileWriter.write("line append2"); + fileWriter.write(System.lineSeparator()); + } + } + @Test + public void appendLine3() throws IOException { + String path = "/Users/darcy/wdbyte/test1.txt"; + try (FileWriter fileWriter = new FileWriter(path, true)) { + BufferedWriter bw = new BufferedWriter(fileWriter); + bw.write("line append3"); + bw.newLine(); + } + } + + @Test + public void appendLine4() throws IOException { + String path = "/Users/darcy/wdbyte/test1.txt"; + try (FileOutputStream fileOutputStream = new FileOutputStream(path, true)) { + fileOutputStream.write("line append4".getBytes(StandardCharsets.UTF_8)); + fileOutputStream.write(System.lineSeparator().getBytes(StandardCharsets.UTF_8)); + } + } + + /** + * java 11 Files.writeString + * @throws IOException + */ + public void appendLineJava11() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/test1.txt"); + Files.writeString(path, "line appendLineJava11", + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } + + + /** + * Java 8 + * + * @throws IOException + */ + @Test + public void appendLineJava8() throws IOException { + Path path = Paths.get("/Users/darcy/wdbyte/test1.txt"); + List dataList = new ArrayList<>(); + dataList.add("line1 appendLineJava8"); + dataList.add("line2 appendLineJava8"); + dataList.add("line3 appendLineJava8"); + + Files.write(path, dataList, + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + + Files.write(path, System.lineSeparator().getBytes(StandardCharsets.UTF_8), + StandardOpenOption.CREATE, + StandardOpenOption.APPEND); + } + + @Test + public void appendLineList() throws IOException { + String path = "/Users/darcy/wdbyte/test1.txt"; + List dataList = new ArrayList<>(); + dataList.add("line1 appendLineList"); + dataList.add("line2 appendLineList"); + dataList.add("line3 appendLineList"); + + try (FileWriter fileWriter = new FileWriter(path, true)) { + BufferedWriter bw = new BufferedWriter(fileWriter); + for (String data : dataList) { + bw.write(data); + bw.newLine(); + } + } + } + + @Test + public void appendByCommonsIo() throws IOException { + File file = new File("/Users/darcy/wdbyte/test1.txt"); + String content = "hello commons io,appendByCommonsIo"; + FileUtils.writeStringToFile(file, content, StandardCharsets.UTF_8, true); + FileUtils.writeStringToFile(file, System.lineSeparator(), StandardCharsets.UTF_8, true); + } + + @Test + public void appendListByCommonsIo() throws IOException { + File file = new File("/Users/darcy/wdbyte/test1.txt"); + List dataList = new ArrayList<>(); + dataList.add("line1 hello commons io,appendListByCommonsIo"); + dataList.add("line2 hello commons io,appendListByCommonsIo"); + dataList.add("line3 hello commons io,appendListByCommonsIo"); + FileUtils.writeLines(file, dataList, true); + } + + @Test + public void appendData(){ + // 创建File对象指向要追加内容的文件 + File file = new File("/Users/darcy/wdbyte/test1.txt"); + // 要追加的内容 + String contentToAppend = "Hello, Guava!"; + try { + // 使用Guava的Files类以追加模式写入内容 + com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND) + .write(contentToAppend); + + // 如果需要追加新行,可以使用下面的代码 + com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, com.google.common.io.FileWriteMode.APPEND) + .write(contentToAppend + System.lineSeparator()); + System.out.println("Content appended successfully."); + } catch (IOException e) { + System.err.println("Error appending content to file: " + e.getMessage()); + } + } + + @Test + public void appendDataListByGuava(){ + // 创建File对象指向要追加内容的文件 + File file = new File("/Users/darcy/wdbyte/test1.txt"); + + // 要追加的内容 + List dataList = new ArrayList<>(); + dataList.add("line1 hello commons io,appendDataListByGuava"); + dataList.add("line2 hello commons io,appendDataListByGuava"); + dataList.add("line3 hello commons io,appendDataListByGuava"); + + try { + // 使用Guava的Files类以追加模式写入内容 + com.google.common.io.Files.asCharSink(file, Charsets.UTF_8, FileWriteMode.APPEND) + .writeLines(dataList); + + System.out.println("Content appended successfully."); + } catch (IOException e) { + System.err.println("Error appending content to file: " + e.getMessage()); + } + } + + + + +} diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java new file mode 100644 index 0000000..5882f01 --- /dev/null +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java @@ -0,0 +1,73 @@ +package com.wdbyte.io.file; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2023/12/18 + */ +public class FileDelete { + + @Test + public void deleteFile() { + Path path = Paths.get("/Users/darcy/wdbyte/test.txt"); + try { + Files.delete(path); + System.out.println("文件删除成功"); + } catch (Exception e) { + System.out.println("文件删除失败"); + e.printStackTrace(); + } + } + + @Test + public void deleteIfExists() { + Path path = Paths.get("/Users/darcy/wdbyte/test.txt"); + try { + boolean deleted = Files.deleteIfExists(path); + if (deleted) { + System.out.println("文件删除成功"); + } else { + System.out.println("文件不存在或者无法删除"); + } + } catch (Exception e) { + System.out.println("文件删除失败"); + e.printStackTrace(); + } + } + + @Test + public void delete() { + File file = new File("/Users/darcy/wdbyte/test.txt"); + if (file.delete()) { + System.out.println("文件删除成功"); + } else { + System.out.println("文件删除失败"); + } + } + + @Test + public void deleteOnExit() { + File file = new File("/Users/darcy/wdbyte/test.txt"); + file.deleteOnExit(); + } + + @Test + public void forceDelete() { + File file = new File("/Users/darcy/wdbyte/test.txt"); + try { + FileUtils.forceDelete(file); + System.out.println("文件删除成功"); + } catch (Exception e) { + System.out.println("文件删除失败"); + e.printStackTrace(); + } + } + +} diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java index 041a6b9..87e35b2 100644 --- a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java @@ -15,6 +15,7 @@ import java.util.Scanner; import java.util.stream.Stream; +import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.Test; /** @@ -165,4 +166,13 @@ public void test8() { e.printStackTrace(); } } + + @Test + public void readFileByApacheCommons() throws IOException { + File file = new File("/Users/darcy/wdbyte/test.txt"); + List list = FileUtils.readLines(file, StandardCharsets.UTF_8); + for (String data : list) { + System.out.println(data); + } + } } From a74ba18aa9d52c603b177b6d0540820ef15e2f39 Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 18 Dec 2023 21:54:16 +0800 Subject: [PATCH 087/105] README: UPDATE --- README.md | 82 +++++++++++++++--------- core-java-modules/core-java-io/README.md | 4 +- 2 files changed, 53 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index fbbfa9a..0784977 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,17 @@ 文章内容也都可以访问网站 [https://www.wdbyte.com](https://www.wdbyte.com) 进行阅读。 - ## ⏳ Java 开发 + +- [如何破解滑动验证码?](https://www.wdbyte.com/java/img-verification/) +- [你好 ChatGPT, 帮我看下这段代码有什么问题?](https://www.wdbyte.com/java/chatgpt-files-list.html) - [JUnit5 单元测试教程](https://www.wdbyte.com/java/junit5.html) - [使用 StringUtils.split 的坑](https://www.wdbyte.com/java/stringutils_split.html) - [必应壁纸,我的第一个 400 Star 开源项目](https://www.wdbyte.com/bing-wallpaper-400.html) - [Java 中的对象池化](https://www.wdbyte.com/java/object-pool.html) - [5种限流算法,7种限流方式,挡住突发流量?](https://www.wdbyte.com/java/rate-limiter.html) - [Java 中拼接 String 的 N 种方式](https://www.wdbyte.com/java/string-concat.html) -- [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) +- [字符作画,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) - [Java 中 RMI 的使用](https://www.wdbyte.com/2021/05/java/java-rmi/) - [如何使用 Github Actions 自动抓取每日必应壁纸?](https://www.wdbyte.com/2021/03/bing-wallpaper-github-action/) - [三种骚操作绕过迭代器遍历时的数据修改异常](https://www.wdbyte.com/2021/02/develop/interator-update/) @@ -35,7 +37,8 @@ - [如何使用 Lombok 进行优雅的编码](https://www.wdbyte.com/2018/12/develop/tool-lombok/) - [使用MyBatis Generator自动生成Model、Dao、Mapper相关代码](https://www.wdbyte.com/2017/11/develop/tool-mybatis-generator/) -## 🌿 Java 基础教程 +## 😍 Java 基础教程 + - [JDK、JRE、JVM 的区别](https://www.wdbyte.com/java/jdk-jre-jvm/) - [Java 数据类型](https://www.wdbyte.com/java/data-type/) - [Java 流程控制](https://www.wdbyte.com/java/flow-control/) @@ -43,24 +46,32 @@ - [Java Array 数组](https://www.wdbyte.com/java/java-array/) - [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/) - [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/) +- [Java Scanner](https://www.wdbyte.com/java/scanner/) - [Java 继承](https://www.wdbyte.com/java/extends/) - [Java 接口](https://www.wdbyte.com/java/interface/) - [Java 抽象类](https://www.wdbyte.com/java/abstract/) +- [抽象类和接口的区别](https://www.wdbyte.com/java/abs-interface/) - [Java 多态](https://www.wdbyte.com/java/polymorphism/) - [Java Scanner](https://www.wdbyte.com/java/scanner/) -- [Java 日期时间 Date](https://www.wdbyte.com/java/date/) +- [Java 日期时间Date](https://www.wdbyte.com/java/date/) - [Java 异常处理](https://www.wdbyte.com/java/exception/) - [Java 枚举](https://www.wdbyte.com/java/enum/) -- [Java 注释](https://www.wdbyte.com/java/comment/) +- [Java 注释](*https://www.wdbyte.com/java/comment/*) - [Java 集合框架](https://www.wdbyte.com/java/collection/) -- [Java 中使用 List](https://www.wdbyte.com/java/list/) +- [Java 中使用 List ](https://www.wdbyte.com/java/list/) + +## 😃Java I/O 教程 -## Java I/O 教程 - [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) - [Java 读取文件](https://www.wdbyte.com/java/io/file-read/) +- [Java 追加内容到文件](https://www.wdbyte.com/java/io/file-append/) +- [Java 如何删除文件](https://www.wdbyte.com/java/io/file-delete/) + +## 🎉 Java 进阶教程 -## Java 进阶 - [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) +- [Java 热加载手动实现](https://www.wdbyte.com/2019/10/jvm/java-hotput/) + ## 🌿 SpringBoot 2.x 教程 @@ -70,9 +81,9 @@ - [Spring Boot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot01-config/) - [Spring Boot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/) - [Spring Boot 系列(四)Spring Boot 日志框架](https://www.wdbyte.com/2019/01/springboot/springboot04-log/) -- [Spring Boot 系列(五)web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/) -- [Spring Boot 系列(六)web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/) -- [Spring Boot 系列(七)web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/) +- [Spring Boot 系列(五)Web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/) +- [Spring Boot 系列(六)Web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/) +- [Spring Boot 系列(七)Web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/) - [Spring Boot 系列(八)动态 Banner 与图片转字符图案的手动实现](https://www.wdbyte.com/2019/02/springboot/springboot-08-banner/) - [Spring Boot 系列(九)使用 Spring JDBC 和 Druid 数据源监控](https://www.wdbyte.com/2019/02/springboot/springboot-09-data-jdbc/) - [Spring Boot 系列(十)使用 Spring data jpa 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-10-data-jpa/) @@ -97,11 +108,11 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Java 20 新功能介绍](https://www.wdbyte.com/java/java-20/) - [Java 19 新功能介绍](https://www.wdbyte.com/java/java-19/) - [Java 18 新功能介绍](https://www.wdbyte.com/java/java-18/) -- [Java 17 新功能介绍 (LTS)](https://www.wdbyte.com/java/java-17/) +- [Java 17 新功能介绍](https://www.wdbyte.com/java/java-17/) - [Java 16 新功能介绍](https://www.wdbyte.com/java/java-16/) - [Java 15 新功能介绍](https://www.wdbyte.com/java/java-15/) -- [Java 14 新特性讲解](https://www.wdbyte.com/java/java-14/) -- [Java 13 新特性讲解](https://www.wdbyte.com/java/java-13/) +- [Java 14 新特性介绍](https://www.wdbyte.com/java/java-14/) +- [Java 13 新特性介绍](https://www.wdbyte.com/java/java-13/) - [Java 12 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk12-feature/) - [Java 11 新特性介绍](https://www.wdbyte.com/2020/03/jdk/jdk11-feature/) - [Java 10 新特性介绍](https://www.wdbyte.com/2020/02/jdk/jdk10-feature/) @@ -112,8 +123,8 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Java 8 函数接口 Supplier ](https://www.wdbyte.com/java8/java8-supplier/) - [Java 8 函数接口 Predicate ](https://www.wdbyte.com/java8/java8-predicate/) - [Java 8 函数接口 Function ](https://www.wdbyte.com/java8/java8-function/) +- [Java 8 Lambda 和 Comparator 排序](https://www.wdbyte.com/java8/comparator/) - [Java 8 新特性 - forEach 遍历](https://www.wdbyte.com/java8/java8-foreach/) - - [Java 8 新特性 - LocalDate、LocalDateTime 时间处理介绍](https://www.wdbyte.com/2019/10/jdk/jdk8-time/) - [Java 8 新特性 - 使用 Optional优雅的处理空指针](https://www.wdbyte.com/2019/11/jdk/jdk8-optional/) - [Java 8 新特性 - Lambda 表达式、函数接口了解一下](https://www.wdbyte.com/2019/11/jdk/jdk8-lambda/) @@ -123,28 +134,36 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 以上 Java 新功能文章源码: [Github.com/niumoo/jdk-feature](https://github.com/niumoo/jdk-feature) -## 🔬 JDK 源码分析 +## 🔬 JDK 源码分析 面试必备的 JDK 源码分析。探寻 JDK 大佬的设计思路。没有链接部分为后续更新内容,持续更新中。 -- [集合 -「源码分析」CopyOnWriteArrayList 中的隐藏的知识,你Get了吗?](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/) -- [集合 -「源码分析」ArrayList和LinkedList如何实现的?我看你还有机会!](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/) -- [集合 -「源码分析」还不懂 ConcurrentHashMap ?这份源码分析了解一下](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/) -- [集合 -「源码分析」最通俗易懂的 HashMap 源码分析解读](https://www.wdbyte.com/2020/03/jdk/hashmap/) -- 集合 -「源码分析」TreeSet -- 集合 -「源码分析」LinkedHashSet +- [集合 - CopyOnWriteArrayList 实现原理和源码分析](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/) +- [集合 - ArrayList和LinkedList 实现原理和源码分析](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/) +- 集合 -「源码分析」Vector +- [集合 - ConcurrentHashMap 实现原理和源码分析](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/) +- [集合 - HashMap 实现原理和源码分析](https://www.wdbyte.com/2020/03/jdk/hashmap/) +- 集合 - TreeMap 实现原理和源码分析 +- 集合 - TreeSet 实现原理和源码分析 +- 集合 - LinkedHashSet 实现原理和源码分析 - 基础类 - Object -- 基础类 - String +- 基础类 - String - 基础类 - StringBuffer & StringBuilder ## 💻 Java 并发编程 -- 线程基础之通知、等待、休眠、让行、中断 -- ThreadLocal +- Java 线程创建与运行 +- Java 线程通知与等待 +- Java 线程休眠与让行 +- Java 线程中断与停止 +- Java 线程死锁 +- Java 线程的上下文切换 +- Java 守护线程与用户线程 +- Java ThreadLocal - 内存可见性、伪共享 -- synchronized -- volatile +- Java synchronized +- Java volatile - 原子操作 - 排它锁、悲观锁、乐观锁、公平锁、非公平锁、独占锁、共享锁、重入锁、自旋锁 - ThreadLocalRandom @@ -170,8 +189,9 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - 锁的自动优化升级策略 ## 🔍 Java 性能分析 - - [Java 中的5个代码性能提升技巧,最高提升9.5倍](https://www.wdbyte.com/java/code-5-tips.html) +- [JMC 使用教程](https://www.wdbyte.com/java/performance/jmc.html) +- [JFR 使用教程](https://www.wdbyte.com/java/performance/jfr.html) - [使用 JMX 监控和管理 Java 程序](https://www.wdbyte.com/java/jmx.html) - [Java 中的监控与管理原理概述](https://www.wdbyte.com/java/monitoring.html) - [JMH-大厂是如何使用JMH进行Java代码性能测试的?必须掌握!](https://www.wdbyte.com/2020/08/develop/tool-jmh/) @@ -201,12 +221,11 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Apache HttpClient 5 使用详细教程](https://www.wdbyte.com/tool/httpclient5.html) - [Jackson 解析 JSON 详细教程](https://www.wdbyte.com/tool/jackson.html) - [Java 反编译工具的使用与对比分析](https://www.wdbyte.com/2021/05/java-decompiler/) -- [可以Postman,也可以cURL.进来领略下cURL的独门绝技](https://www.wdbyte.com/2020/06/tool/curl/) -- [抛弃Eclipse,投入IDEA 的独孤求败江湖](https://www.wdbyte.com/2019/10/develop/idea-skill/) +- [cURL 使用教程](https://www.wdbyte.com/2020/06/tool/curl/) +- [Java IDEA 使用教程](https://www.wdbyte.com/2019/10/develop/idea-skill/) - [使用Apache Ant 进行Java web项目打包并部署至TOMCAT](https://www.wdbyte.com/2017/11/develop/tool-apache-ant/) - [Linux配置Tomcat的单机多实例](https://www.wdbyte.com/2018/08/develop/install-tomcat-many-instance/) - [Linux定时任务crontab的使用](https://www.wdbyte.com/2018/05/linux/linux-crontab/) -- [原来热加载如此简单,手动写一个 Java 热加载吧](https://www.wdbyte.com/2019/10/jvm/java-hotput/) - [Manjaro Linux 入门使用教程](https://www.wdbyte.com/2020/04/linux/linux-manjaro/) - [Ubuntu18 的超详细常用软件安装](https://www.wdbyte.com/2018/11/linux/start-ubuntu/) @@ -220,7 +239,6 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [IO通信模型(二)同步非阻塞模式NIO(NonBlocking IO)](https://www.wdbyte.com/2018/10/io/io2-nio/) - [IO通信模型(一)同步阻塞模式BIO(Blocking IO)](https://www.wdbyte.com/2018/10/io/io1-bio/) - ## 🗺 贡献与建议 1. 内容难免存在笔误,一个错别字,一个语法错误,都是贡献。 diff --git a/core-java-modules/core-java-io/README.md b/core-java-modules/core-java-io/README.md index 8ebf834..85360a7 100644 --- a/core-java-modules/core-java-io/README.md +++ b/core-java-modules/core-java-io/README.md @@ -2,6 +2,8 @@ 当前模块包含 IO 相关代码 ### 相关文章 -- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/) - [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) +- [Java 读取文件](https://www.wdbyte.com/java/io/file-read/) +- [Java 追加内容到文件](https://www.wdbyte.com/java/io/file-append/) +- [Java 如何删除文件](https://www.wdbyte.com/java/io/file-delete/) - [字符图案,我用字符画个冰墩墩](https://www.wdbyte.com/java/char-image.html) \ No newline at end of file From 0fae92c5eb8bd76adcecc159ab987b711a3a4300 Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 27 Feb 2024 20:10:26 +0800 Subject: [PATCH 088/105] feat: add springboot+jpa+sqlite --- springboot/springboot-sqlite-jpa/README.md | 19 ++ springboot/springboot-sqlite-jpa/mvnw | 308 ++++++++++++++++++ springboot/springboot-sqlite-jpa/mvnw.cmd | 205 ++++++++++++ springboot/springboot-sqlite-jpa/pom.xml | 81 +++++ .../springsqlite/SpringBootSqliteApp.java | 13 + .../controller/SqliteController.java | 66 ++++ .../springsqlite/model/WebsiteUser.java | 43 +++ .../repository/WebsiteUserRepository.java | 11 + .../src/main/resources/application.properties | 7 + .../SpringbootSqliteJpaApplicationTests.java | 13 + 10 files changed, 766 insertions(+) create mode 100644 springboot/springboot-sqlite-jpa/README.md create mode 100755 springboot/springboot-sqlite-jpa/mvnw create mode 100644 springboot/springboot-sqlite-jpa/mvnw.cmd create mode 100644 springboot/springboot-sqlite-jpa/pom.xml create mode 100644 springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java create mode 100644 springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java create mode 100644 springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java create mode 100644 springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java create mode 100644 springboot/springboot-sqlite-jpa/src/main/resources/application.properties create mode 100644 springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java diff --git a/springboot/springboot-sqlite-jpa/README.md b/springboot/springboot-sqlite-jpa/README.md new file mode 100644 index 0000000..bf0984a --- /dev/null +++ b/springboot/springboot-sqlite-jpa/README.md @@ -0,0 +1,19 @@ +# Srping Boot + JPA + Sqlite + +### Reference Documentation +For further reference, please consider the following sections: + +* [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) +* [Spring Boot Maven Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/3.2.3/maven-plugin/reference/html/) +* [Create an OCI image](https://docs.spring.io/spring-boot/docs/3.2.3/maven-plugin/reference/html/#build-image) +* [Spring Web](https://docs.spring.io/spring-boot/docs/3.2.3/reference/htmlsingle/index.html#web) +* [Spring Data JPA](https://docs.spring.io/spring-boot/docs/3.2.3/reference/htmlsingle/index.html#data.sql.jpa-and-spring-data) + +### Guides +The following guides illustrate how to use some features concretely: + +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) + diff --git a/springboot/springboot-sqlite-jpa/mvnw b/springboot/springboot-sqlite-jpa/mvnw new file mode 100755 index 0000000..66df285 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/springboot/springboot-sqlite-jpa/mvnw.cmd b/springboot/springboot-sqlite-jpa/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/springboot/springboot-sqlite-jpa/pom.xml b/springboot/springboot-sqlite-jpa/pom.xml new file mode 100644 index 0000000..47d85ed --- /dev/null +++ b/springboot/springboot-sqlite-jpa/pom.xml @@ -0,0 +1,81 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + com.wdbyte + springboot-sqlite-jpa + 0.0.1-SNAPSHOT + springboot-sqlite-jpa + Demo project for Spring Boot + + 21 + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.hibernate.orm + hibernate-community-dialects + 6.4.3.Final + + + + org.xerial + sqlite-jdbc + 3.45.1.0 + + + org.projectlombok + lombok + true + + + org.apache.commons + commons-lang3 + 3.13.0 + + + commons-codec + commons-codec + 1.16.0 + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java new file mode 100644 index 0000000..d238e29 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java @@ -0,0 +1,13 @@ +package com.wdbyte.springsqlite; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringBootSqliteApp { + + public static void main(String[] args) { + SpringApplication.run(SpringBootSqliteApp.class, args); + } + +} diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java new file mode 100644 index 0000000..132d69c --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java @@ -0,0 +1,66 @@ +package com.wdbyte.springsqlite.controller; + +import java.time.LocalDateTime; + +import com.wdbyte.springsqlite.model.WebsiteUser; +import com.wdbyte.springsqlite.repository.WebsiteUserRepository; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author niulang + * @date 2024/02/27 + */ +@Slf4j +@RestController +public class SqliteController { + + @Autowired + private WebsiteUserRepository userRepository; + + @GetMapping("/sqlite/init") + public String init() { + for (int i = 0; i < 10; i++) { + WebsiteUser websiteUser = new WebsiteUser(); + // 随机4个字母 + websiteUser.setUsername(RandomStringUtils.randomAlphabetic(4)); + // 随机16个字符用于密码加盐加密 + websiteUser.setSalt(RandomStringUtils.randomAlphanumeric(16)); + String password = "123456"; + // 密码存储 = md5(密码+盐) + password = password + websiteUser.getSalt(); + websiteUser.setPassword(DigestUtils.md5Hex(password)); + websiteUser.setCreatedAt(LocalDateTime.now()); + websiteUser.setUpdatedAt(LocalDateTime.now()); + websiteUser.setStatus("active"); + WebsiteUser saved = userRepository.save(websiteUser); + log.info("init user {}", saved.getUsername()); + } + return "init success"; + } + + @GetMapping("/sqlite/find") + public String findByUsername(String username) { + WebsiteUser websiteUser = userRepository.findByUsername(username); + return websiteUser.toString(); + } + + @GetMapping("/sqlite/login") + public String findByUsername(String username, String password) { + WebsiteUser websiteUser = userRepository.findByUsername(username); + if (websiteUser == null) { + return "login failed"; + } + password = password + websiteUser.getSalt(); + if (StringUtils.equals(DigestUtils.md5Hex(password), websiteUser.getPassword())) { + return "login succeeded"; + } else { + return "login failed"; + } + } +} diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java new file mode 100644 index 0000000..264eec0 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/model/WebsiteUser.java @@ -0,0 +1,43 @@ +package com.wdbyte.springsqlite.model; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Getter +@Setter +@ToString +@Table(name = "website_user") +public class WebsiteUser { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column(name = "username", nullable = false, unique = true, length = 64) + private String username; + + @Column(name = "password", nullable = false, length = 255) + private String password; + + @Column(name = "salt", nullable = false, length = 16) + private String salt; + + @Column(name = "status", nullable = false, length = 16, columnDefinition = "VARCHAR(16) DEFAULT 'active'") + private String status; + + @Column(name = "created_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + private LocalDateTime createdAt; + + @Column(name = "updated_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP") + private LocalDateTime updatedAt; +} diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java new file mode 100644 index 0000000..7f830a2 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java @@ -0,0 +1,11 @@ +package com.wdbyte.springsqlite.repository; + +import com.wdbyte.springsqlite.model.WebsiteUser; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface WebsiteUserRepository extends CrudRepository { + + WebsiteUser findByUsername(String name); +} \ No newline at end of file diff --git a/springboot/springboot-sqlite-jpa/src/main/resources/application.properties b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties new file mode 100644 index 0000000..fa5d94e --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties @@ -0,0 +1,7 @@ +spring.datasource.url=jdbc:sqlite:springboot-sqlite-jpa.db +spring.datasource.driver-class-name=org.sqlite.JDBC + +# JPA Properties +spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true diff --git a/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java b/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java new file mode 100644 index 0000000..7d48850 --- /dev/null +++ b/springboot/springboot-sqlite-jpa/src/test/java/com/wdbyte/springbootsqlitejpa/SpringbootSqliteJpaApplicationTests.java @@ -0,0 +1,13 @@ +package com.wdbyte.springbootsqlitejpa; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SpringbootSqliteJpaApplicationTests { + + @Test + void contextLoads() { + } + +} From 39c0d444bedb4ba28ca322d94863a8c02b784c0e Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 27 Feb 2024 23:43:46 +0800 Subject: [PATCH 089/105] feat: add springboot+jpa+sqlite --- .../java/com/wdbyte/springsqlite/SpringBootSqliteApp.java | 3 +++ .../wdbyte/springsqlite/controller/SqliteController.java | 6 ++++-- .../springsqlite/repository/WebsiteUserRepository.java | 5 +++++ .../src/main/resources/application.properties | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java index d238e29..f54d63a 100644 --- a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/SpringBootSqliteApp.java @@ -3,6 +3,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +/** + * @author https://www.wdbyte.com + */ @SpringBootApplication public class SpringBootSqliteApp { diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java index 132d69c..b78d228 100644 --- a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/controller/SqliteController.java @@ -13,8 +13,7 @@ import org.springframework.web.bind.annotation.RestController; /** - * @author niulang - * @date 2024/02/27 + * @author https://www.wdbyte.com */ @Slf4j @RestController @@ -47,6 +46,9 @@ public String init() { @GetMapping("/sqlite/find") public String findByUsername(String username) { WebsiteUser websiteUser = userRepository.findByUsername(username); + if (websiteUser == null) { + return null; + } return websiteUser.toString(); } diff --git a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java index 7f830a2..12b6af0 100644 --- a/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java +++ b/springboot/springboot-sqlite-jpa/src/main/java/com/wdbyte/springsqlite/repository/WebsiteUserRepository.java @@ -7,5 +7,10 @@ @Repository public interface WebsiteUserRepository extends CrudRepository { + /** + * 根据 username 查询数据 + * @param name + * @return + */ WebsiteUser findByUsername(String name); } \ No newline at end of file diff --git a/springboot/springboot-sqlite-jpa/src/main/resources/application.properties b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties index fa5d94e..3ee254a 100644 --- a/springboot/springboot-sqlite-jpa/src/main/resources/application.properties +++ b/springboot/springboot-sqlite-jpa/src/main/resources/application.properties @@ -1,7 +1,7 @@ spring.datasource.url=jdbc:sqlite:springboot-sqlite-jpa.db spring.datasource.driver-class-name=org.sqlite.JDBC - # JPA Properties spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect +# create ?????????update????????? spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true +spring.jpa.show-sql=true \ No newline at end of file From f5a1d62f269da4e199b4e3a7742b2e0992e5410e Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 28 Feb 2024 15:04:30 +0800 Subject: [PATCH 090/105] feat: add springboot+jpa+sqlite --- .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar create mode 100644 springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties diff --git a/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/springboot/springboot-sqlite-jpa/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar From 78c287c82ca0ad2fec8157bf4d6d8f448c93c297 Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 28 Feb 2024 19:41:21 +0800 Subject: [PATCH 091/105] update readme.md --- README.md | 14 ++++++++++--- springboot/springboot-sqlite-jpa/README.md | 23 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0784977..f1456ed 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,10 @@ 未读代码

-目录中没有链接的部分,后续更新,感谢你的 ​STAR​ ,有问题或者建议可以[一起完善](Accept#-%E8%B4%A1%E7%8C%AE%E4%B8%8E%E5%BB%BA%E8%AE%AE)。 -文章内容也都可以访问网站 [https://www.wdbyte.com](https://www.wdbyte.com) 进行阅读。 +目录中没有链接的部分,后续更新,感谢你的关注 ,有问题或者建议可以[一起完善](#🗺-贡献与建议)。 +> Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。 + ## ⏳ Java 开发 @@ -96,6 +97,7 @@ - [Spring Boot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序](https://www.wdbyte.com/2019/12/springboot/springboot-17-admin/) - [Spring Boot 系列(十八)最详细的 Spring Boot 多模块开发与排坑指南](https://www.wdbyte.com/2020/03/springboot/springboot-18-module/) - [Spring Boot 系列(十九)SpringBoot 的多数据源配置](https://www.wdbyte.com/2020/12/springboot/springboot-multiple-datasource/) +- [Spring Boot 系列(二十)Spring Boot,JPA与SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/) 以上 Spring Boot 文章源码:[Github.com/niumoo/springboot](https://github.com/niumoo/springboot/) @@ -209,6 +211,9 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - 堆 - 图 +## 🍔 数据库 +- [SQLite 入门教程](https://www.wdbyte.com/db/sqlite/) + ## 🧰 工具技巧 >“工欲善其事,必先利其器” @@ -239,9 +244,12 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [IO通信模型(二)同步非阻塞模式NIO(NonBlocking IO)](https://www.wdbyte.com/2018/10/io/io2-nio/) - [IO通信模型(一)同步阻塞模式BIO(Blocking IO)](https://www.wdbyte.com/2018/10/io/io1-bio/) + ## 🗺 贡献与建议 -1. 内容难免存在笔误,一个错别字,一个语法错误,都是贡献。 +反馈地址:[https://github.com/niumoo/JavaNotes/issues](https://github.com/niumoo/JavaNotes/issues) + +1. 内容难免存在笔误,一个错别字,一个语法错误,都是建议。 2. 文章中的错误和不足,或者不完善的地方都可以进行补充或者修改。 3. 我没有涉及到的知识点,也可以进行补充。 diff --git a/springboot/springboot-sqlite-jpa/README.md b/springboot/springboot-sqlite-jpa/README.md index bf0984a..5be62f4 100644 --- a/springboot/springboot-sqlite-jpa/README.md +++ b/springboot/springboot-sqlite-jpa/README.md @@ -1,5 +1,28 @@ # Srping Boot + JPA + Sqlite +使用 **Spring Boot** 可以快速的创建一个基于Spring 的、独立的、生产级的应用程序,并且可以直接运行。Spring Boot 采用习惯性配置,整合大量 Spring 组建和第三方库,让你只需要少量的修改就可以轻松上手。 + +- [Spring Boot 系列(一)Spring Boot 入门篇](https://www.wdbyte.com/2019/01/springboot/springboot01-quick-start/) +- [Spring Boot 系列(二)Spring Boot 配置文件](https://www.wdbyte.com/2019/01/springboot/springboot01-config/) +- [Spring Boot 系列(三)Spring Boot 自动配置](https://www.wdbyte.com/2019/01/springboot/springboot03-auto-config/) +- [Spring Boot 系列(四)Spring Boot 日志框架](https://www.wdbyte.com/2019/01/springboot/springboot04-log/) +- [Spring Boot 系列(五)Web 开发之静态资源和模版引擎](https://www.wdbyte.com/2019/02/springboot/springboot-05-web-static-template/) +- [Spring Boot 系列(六)Web 开发之拦截器和三大组件](https://www.wdbyte.com/2019/02/springboot/springboot-06-web-filter-apo-webbase/) +- [Spring Boot 系列(七)Web 开发之异常错误处理机制剖析](https://www.wdbyte.com/2019/02/springboot/springboot-07-web-exception/) +- [Spring Boot 系列(八)动态 Banner 与图片转字符图案的手动实现](https://www.wdbyte.com/2019/02/springboot/springboot-08-banner/) +- [Spring Boot 系列(九)使用 Spring JDBC 和 Druid 数据源监控](https://www.wdbyte.com/2019/02/springboot/springboot-09-data-jdbc/) +- [Spring Boot 系列(十)使用 Spring data jpa 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-10-data-jpa/) +- [Spring Boot 系列(十一)使用 Mybatis(自动生成插件) 访问数据库](https://www.wdbyte.com/2019/03/springboot/springboot-11-data-mybatis/) +- [Spring Boot 系列(十二)使用 Mybatis 集成 pagehelper 分页插件和 mapper 插件](https://www.wdbyte.com/2019/03/springboot/springboot-12-data-mybatis-page/) +- [Spring Boot 系列(十三)使用邮件服务](https://www.wdbyte.com/2019/03/springboot/springboot-13-email/) +- [Spring Boot 系列(十四)迅速启用 HTTPS 加密你的网站](https://www.wdbyte.com/2019/08/springboot/springboot-14-https/) +- [Spring Boot 系列(十五)如何编写自己的 Springboot starter](https://www.wdbyte.com/2019/11/springboot/springboot-15-my-starter/) +- [Spring Boot 系列(十六)你真的了解 Swagger 文档吗?](https://www.wdbyte.com/2019/11/springboot/springboot-16-web-swagger/) +- [Spring Boot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序](https://www.wdbyte.com/2019/12/springboot/springboot-17-admin/) +- [Spring Boot 系列(十八)最详细的 Spring Boot 多模块开发与排坑指南](https://www.wdbyte.com/2020/03/springboot/springboot-18-module/) +- [Spring Boot 系列(十九)SpringBoot 的多数据源配置](https://www.wdbyte.com/2020/12/springboot/springboot-multiple-datasource/) +- [Spring Boot 系列(二十)Spring Boot,JPA与SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/) +- ### Reference Documentation For further reference, please consider the following sections: From d69973d0babeeecc5e172c0cc094e4de1d184c8b Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 5 Mar 2024 11:43:53 +0800 Subject: [PATCH 092/105] code: add java arrays demo --- .../com/wdbyte/collection/EnumMapTest.java | 25 +++ .../com/wdbyte/collection/JavaArrays.java | 146 ++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java create mode 100644 core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java new file mode 100644 index 0000000..4646e7d --- /dev/null +++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java @@ -0,0 +1,25 @@ +package com.wdbyte.collection; + +import java.util.EnumMap; + +/** + * @author niulang + * @date 2023/10/20 + */ +public class EnumMapTest { + public static void main(String[] args) { + EnumMap enumMap = new EnumMap(Week.class); + enumMap.put(Week.a,null); + System.out.println(enumMap.get(Week.a)); + } +} + +enum Week { + a, + b, + c, + d, + e, + f, + g +} diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java new file mode 100644 index 0000000..47da76b --- /dev/null +++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java @@ -0,0 +1,146 @@ +package com.wdbyte.collection; + +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.function.IntFunction; +import java.util.function.ToIntFunction; + +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2024/03/04 + */ +public class JavaArrays { + + @Test + public void print() { + String[] arr = new String[] {"a", "b", "c", "d"}; + System.out.println(Arrays.toString(arr)); + } + + @Test + public void init() { + String[] arr = new String[] {"a", "b", "c", "d"}; + String[] copyOf2 = Arrays.copyOf(arr, 2); + String[] copyOfRange = Arrays.copyOfRange(arr, 1, 3); + + System.out.println(Arrays.toString(arr)); + System.out.println(Arrays.toString(copyOf2)); + System.out.println(Arrays.toString(copyOfRange)); + + // 目标大小大于原始大小,则copyOf 会用null填充数组 。 + String[] copyOf10 = Arrays.copyOf(arr, 10); + System.out.println(Arrays.toString(copyOf10)); + } + + @Test + public void fill() { + String[] arr = new String[5]; + Arrays.fill(arr, "java"); + System.out.println(Arrays.toString(arr)); + + // 生成 100以内的 随机数 + IntFunction intFunction = i -> new Random().nextInt(100); + Integer[] intArr = new Integer[5]; + Arrays.setAll(intArr, intFunction); + System.out.println(Arrays.toString(intArr)); + } + + @Test + public void diff() { + String[] arr = new String[] {"a", "b", "c", "d"}; + Object[] arr1 = new Object[] {arr, new String[] {"a", "b", "c", "d"}}; + Object[] arr2 = new Object[] {arr, arr}; + + System.out.println(Arrays.equals(arr1, arr2)); + System.out.println(Arrays.deepEquals(arr1, arr2)); + + // hashCode + System.out.println(Arrays.hashCode(arr2)); + System.out.println(Arrays.deepHashCode(arr2)); + + arr[0] = null; + System.out.println(Arrays.hashCode(arr2)); + System.out.println(Arrays.deepHashCode(arr2)); + } + + @Test + public void sort() { + // 生成 100以内的 随机数 + IntFunction intFunction = i -> new Random().nextInt(100); + Integer[] intArr = new Integer[5]; + Arrays.setAll(intArr, intFunction); + System.out.println(Arrays.toString(intArr)); + + Arrays.sort(intArr); + System.out.println(Arrays.toString(intArr)); + + //long cost = 0; + //for (int ii = 0; ii < 10; ii++) { + // IntFunction intFunction = i -> new Random().nextInt(100000); + // Integer[] intArr = new Integer[100000]; + // Arrays.setAll(intArr, intFunction); + // + // long start = System.currentTimeMillis(); + // Arrays.sort(intArr); + // long end = System.currentTimeMillis(); + // cost = cost + (end - start); + //} + //System.out.println(cost / 10); + // + //cost = 0; + //for (int ii = 0; ii < 10; ii++) { + // IntFunction intFunction = i -> new Random().nextInt(100000); + // Integer[] intArr = new Integer[100000]; + // Arrays.setAll(intArr, intFunction); + // + // long start = System.currentTimeMillis(); + // Arrays.parallelSort(intArr); + // long end = System.currentTimeMillis(); + // cost = cost + (end - start); + //} + //System.out.println(cost / 10); + } + + @Test + public void search() { + Integer[] intArr = new Integer[] {2, 3, 4, 5, 6, 7, 8, 9}; + int index = Arrays.binarySearch(intArr, 3); + System.out.println("index:"+index); + System.out.println(intArr[index]); + } + + @Test + public void stream() { + Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + System.out.println(Arrays.stream(intArr).count()); + ToIntFunction toIntFunction = i -> (int)i; + System.out.println(Arrays.stream(intArr).mapToInt(toIntFunction).sum()); + } + + @Test + public void cast() { + String[] arr = new String[] {"a", "b", "c", "d"}; + Object[] arr2 = new Object[] {arr, arr}; + + System.out.println(Arrays.toString(arr)); + System.out.println(Arrays.toString(arr2)); + + System.out.println(Arrays.deepToString(arr)); + System.out.println(Arrays.deepToString(arr2)); + + List list = Arrays.asList(arr); + System.out.println(list); + System.out.println(list.getClass()); + // list.add("e"); 报错 + } + + @Test + public void prefix() { + Integer[] intArr = new Integer[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + Arrays.parallelPrefix(intArr, (left, right) -> left + right); + System.out.println(Arrays.toString(intArr)); + } +} From bb9512ff9406ac457b49f0a409b9f5d0e6a439f5 Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 6 Mar 2024 20:07:45 +0800 Subject: [PATCH 093/105] feat: add springboot hello demo --- .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + springboot/springboot-hello/README.md | 1 + springboot/springboot-hello/mvnw | 308 ++++++++++++++++++ springboot/springboot-hello/mvnw.cmd | 205 ++++++++++++ springboot/springboot-hello/pom.xml | 35 ++ .../com/wdbyte/start/HelloController.java | 20 ++ .../java/com/wdbyte/start/SpringBootApp.java | 16 + .../src/main/resources/application.properties | 1 + 9 files changed, 588 insertions(+) create mode 100644 springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar create mode 100644 springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties create mode 100644 springboot/springboot-hello/README.md create mode 100755 springboot/springboot-hello/mvnw create mode 100644 springboot/springboot-hello/mvnw.cmd create mode 100644 springboot/springboot-hello/pom.xml create mode 100644 springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java create mode 100644 springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java create mode 100644 springboot/springboot-hello/src/main/resources/application.properties diff --git a/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/springboot/springboot-hello/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/springboot/springboot-hello/README.md b/springboot/springboot-hello/README.md new file mode 100644 index 0000000..c038db4 --- /dev/null +++ b/springboot/springboot-hello/README.md @@ -0,0 +1 @@ +# Srping Boot 简单 Web 接口 diff --git a/springboot/springboot-hello/mvnw b/springboot/springboot-hello/mvnw new file mode 100755 index 0000000..66df285 --- /dev/null +++ b/springboot/springboot-hello/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/springboot/springboot-hello/mvnw.cmd b/springboot/springboot-hello/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/springboot/springboot-hello/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/springboot/springboot-hello/pom.xml b/springboot/springboot-hello/pom.xml new file mode 100644 index 0000000..343a330 --- /dev/null +++ b/springboot/springboot-hello/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + com.wdbyte + springboot-hello + 0.0.1-SNAPSHOT + springboot-hello + Demo project for Spring Boot + + 21 + + + + org.springframework.boot + spring-boot-starter-web + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java b/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java new file mode 100644 index 0000000..e887c46 --- /dev/null +++ b/springboot/springboot-hello/src/main/java/com/wdbyte/start/HelloController.java @@ -0,0 +1,20 @@ +package com.wdbyte.start; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author https://www.wdbyte.com + */ +@RestController +public class HelloController { + + @GetMapping("/hello") + public String hello(String username) { + if (username == null) { + return "Hello,Who are you?"; + } + return "Hello," + username; + } + +} diff --git a/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java b/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java new file mode 100644 index 0000000..6f46204 --- /dev/null +++ b/springboot/springboot-hello/src/main/java/com/wdbyte/start/SpringBootApp.java @@ -0,0 +1,16 @@ +package com.wdbyte.start; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author https://www.wdbyte.com + */ +@SpringBootApplication +public class SpringBootApp { + + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } + +} diff --git a/springboot/springboot-hello/src/main/resources/application.properties b/springboot/springboot-hello/src/main/resources/application.properties new file mode 100644 index 0000000..a3ac65c --- /dev/null +++ b/springboot/springboot-hello/src/main/resources/application.properties @@ -0,0 +1 @@ +server.port=8080 \ No newline at end of file From 7963384049d4ed0dbe341f8520393591664bb924 Mon Sep 17 00:00:00 2001 From: niumoo Date: Fri, 8 Mar 2024 09:06:42 +0800 Subject: [PATCH 094/105] update README.md --- README.md | 48 ++++++++++++++++++++---------------------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index f1456ed..baab1c6 100644 --- a/README.md +++ b/README.md @@ -44,20 +44,27 @@ - [Java 数据类型](https://www.wdbyte.com/java/data-type/) - [Java 流程控制](https://www.wdbyte.com/java/flow-control/) - [Java String 字符串](https://www.wdbyte.com/java/java-string/) -- [Java Array 数组](https://www.wdbyte.com/java/java-array/) -- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/) - [Java StringBuilder](https://www.wdbyte.com/java/java-stringbuilder/) - [Java Scanner](https://www.wdbyte.com/java/scanner/) -- [Java 继承](https://www.wdbyte.com/java/extends/) -- [Java 接口](https://www.wdbyte.com/java/interface/) -- [Java 抽象类](https://www.wdbyte.com/java/abstract/) -- [抽象类和接口的区别](https://www.wdbyte.com/java/abs-interface/) - [Java 多态](https://www.wdbyte.com/java/polymorphism/) - [Java Scanner](https://www.wdbyte.com/java/scanner/) - [Java 日期时间Date](https://www.wdbyte.com/java/date/) - [Java 异常处理](https://www.wdbyte.com/java/exception/) - [Java 枚举](https://www.wdbyte.com/java/enum/) - [Java 注释](*https://www.wdbyte.com/java/comment/*) + +### Java 数组 +- [Java Array 数组](https://www.wdbyte.com/java/java-array/) +- [Java 多维数组](https://www.wdbyte.com/java/java-array-mul/) +- [Java Arrays 教程](https://www.wdbyte.com/java/arrays/) + +### Java 面向对象 +- [Java 继承](https://www.wdbyte.com/java/extends/) +- [Java 接口](https://www.wdbyte.com/java/interface/) +- [Java 抽象类](https://www.wdbyte.com/java/abstract/) +- [抽象类和接口的区别](https://www.wdbyte.com/java/abs-interface/) + +### Java 集合 - [Java 集合框架](https://www.wdbyte.com/java/collection/) - [Java 中使用 List ](https://www.wdbyte.com/java/list/) @@ -72,6 +79,7 @@ - [ProcessBuilder API 使用教程](https://www.wdbyte.com/java/os/processbuilder/) - [Java 热加载手动实现](https://www.wdbyte.com/2019/10/jvm/java-hotput/) +- [Jpackage - 制作无需预装 Java 环境的 Jar 可执行程序](https://www.wdbyte.com/java/jpackage/) ## 🌿 SpringBoot 2.x 教程 @@ -97,7 +105,7 @@ - [Spring Boot 系列(十七)迅速使用 Spring Boot Admin 监控你的 Spring Boot 程序](https://www.wdbyte.com/2019/12/springboot/springboot-17-admin/) - [Spring Boot 系列(十八)最详细的 Spring Boot 多模块开发与排坑指南](https://www.wdbyte.com/2020/03/springboot/springboot-18-module/) - [Spring Boot 系列(十九)SpringBoot 的多数据源配置](https://www.wdbyte.com/2020/12/springboot/springboot-multiple-datasource/) -- [Spring Boot 系列(二十)Spring Boot,JPA与SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/) +- [Spring Boot 系列(二十)三分钟,Spring Boot、JPA 与 SQLite 的快速启动](https://www.wdbyte.com/springboot/sqlite/) 以上 Spring Boot 文章源码:[Github.com/niumoo/springboot](https://github.com/niumoo/springboot/) @@ -253,31 +261,15 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 2. 文章中的错误和不足,或者不完善的地方都可以进行补充或者修改。 3. 我没有涉及到的知识点,也可以进行补充。 -## 🏃 我的痕迹 - -1. 我的网站:[https://www.wdbyte.com/](https://www.wdbyte.com/) - -2. GitHub:[https://github.com/niumoo](https://github.com/niumoo) - -3. C SDN:[https://blog.csdn.net/u013735734](https://blog.csdn.net/u013735734) - -4. 博客园:[https://www.cnblogs.com/niumoo/](https://www.cnblogs.com/niumoo/) -5. 掘 金:[https://juejin.im/user/5a62d481f265da3e2a0dac9b](https://juejin.im/user/5a62d481f265da3e2a0dac9b) +### 公众号 -6. 知 乎:[https://www.zhihu.com/people/bpdwn](https://www.zhihu.com/people/bpdwn) +可以关注「 **程序猿阿朗** 」公众号。即使查看更新的文章以及分享的干货。 + 等你很久 ### 联系我 -可以添加我的微信 wn8398 一起交流。 - -交个朋友 - -### 公众号 - -有帮助可以点「**赞**」在看或 :star: **Star**,谢谢你! - -如果大家想要实时关注我更新的文章以及分享的干货的话,可以关注「 **未读代码** 」公众号。 +等不及了,还不添加我微信一起交个朋友。 -等你很久 +交个朋友 \ No newline at end of file From 7ac14dcb05dad99c8fcd52d729730604057338f7 Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 19 Mar 2024 20:29:41 +0800 Subject: [PATCH 095/105] update README.md --- README.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index baab1c6..4cf43e1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@

+ 目录中没有链接的部分,后续更新,感谢你的关注 ,有问题或者建议可以[一起完善](#🗺-贡献与建议)。 > Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。 @@ -142,25 +143,6 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Java 7 新特性 - 和低效 IO 说再见,Files,Paths,Path 文件操作介绍](https://www.wdbyte.com/2020/09/jdk/jdk7-file-pahs/) - [Java 7 新特性 - 新特性 - 快来补一波 Java 7 语法特性](https://www.wdbyte.com/2020/01/jdk/jdk7-start/) -以上 Java 新功能文章源码: [Github.com/niumoo/jdk-feature](https://github.com/niumoo/jdk-feature) - -## 🔬 JDK 源码分析 - -面试必备的 JDK 源码分析。探寻 JDK 大佬的设计思路。没有链接部分为后续更新内容,持续更新中。 - -- [集合 - CopyOnWriteArrayList 实现原理和源码分析](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/) -- [集合 - ArrayList和LinkedList 实现原理和源码分析](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/) -- 集合 -「源码分析」Vector -- [集合 - ConcurrentHashMap 实现原理和源码分析](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/) -- [集合 - HashMap 实现原理和源码分析](https://www.wdbyte.com/2020/03/jdk/hashmap/) -- 集合 - TreeMap 实现原理和源码分析 -- 集合 - TreeSet 实现原理和源码分析 -- 集合 - LinkedHashSet 实现原理和源码分析 -- 基础类 - Object -- 基础类 - String -- 基础类 - StringBuffer & StringBuilder - - ## 💻 Java 并发编程 - Java 线程创建与运行 @@ -208,6 +190,26 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 - [Arthas - Java 线上问题定位处理的终极利器](https://www.wdbyte.com/2019/11/arthas/) - [超好用的自带火焰图的 Java 性能分析工具 Async-profiler 了解一下](https://www.wdbyte.com/2019/12/async-profiler/) +## 🔬 JDK 源码分析 + +面试必备的 JDK 源码分析。探寻 JDK 大佬的设计思路。没有链接部分为后续更新内容,持续更新中。 + +- [集合 - CopyOnWriteArrayList 实现原理和源码分析](https://www.wdbyte.com/2020/10/jdk/src-copyonwritearraylist/) +- [集合 - ArrayList和LinkedList 实现原理和源码分析](https://www.wdbyte.com/2020/08/jdk/src-arraylist-linkedlist/) +- 集合 -「源码分析」Vector +- [集合 - ConcurrentHashMap 实现原理和源码分析](https://www.wdbyte.com/2020/04/jdk/concurrent-hashmap/) +- [集合 - HashMap 实现原理和源码分析](https://www.wdbyte.com/2020/03/jdk/hashmap/) +- 集合 - TreeMap 实现原理和源码分析 +- 集合 - TreeSet 实现原理和源码分析 +- 集合 - LinkedHashSet 实现原理和源码分析 +- 基础类 - Object +- 基础类 - String +- 基础类 - StringBuffer & StringBuilder + +## 认证授权 +- [JSON Web Token 入门教程](https://www.wdbyte.com/auth/jwt/) + + ## 🧱 数据结构 - 数组 @@ -272,4 +274,4 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 等不及了,还不添加我微信一起交个朋友。 -交个朋友 \ No newline at end of file +交个朋友 From 07be111fb8cdc1bada0fd68b0f8c676871905c18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Wed, 20 Mar 2024 19:28:20 +0800 Subject: [PATCH 096/105] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 4cf43e1..25f3fac 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ > Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。 +## 赏个 Star + +[![Star History Chart](https://api.star-history.com/svg?repos=niumoo/JavaNotes&type=Date)](https://star-history.com/#niumoo/JavaNotes&Date) + ## ⏳ Java 开发 - [如何破解滑动验证码?](https://www.wdbyte.com/java/img-verification/) From fb08995bed8d61a00307cc77897882d8416897ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Wed, 20 Mar 2024 19:29:43 +0800 Subject: [PATCH 097/105] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25f3fac..1f330c6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,8 @@ ## 赏个 Star -[![Star History Chart](https://api.star-history.com/svg?repos=niumoo/JavaNotes&type=Date)](https://star-history.com/#niumoo/JavaNotes&Date) +[![Stargazers over time](https://starchart.cc/niumoo/JavaNotes.svg?background=%23FFFFFF&axis=%23333333&line=%23e76060)](https://starchart.cc/niumoo/JavaNotes) + ## ⏳ Java 开发 From 6bc22c2a090e0707a27498257911828e204759db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A8=8B=E5=BA=8F=E7=8C=BF=E9=98=BF=E6=9C=97?= Date: Wed, 20 Mar 2024 19:30:12 +0800 Subject: [PATCH 098/105] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1f330c6..1ec435e 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,6 @@ > Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。 -## 赏个 Star - -[![Stargazers over time](https://starchart.cc/niumoo/JavaNotes.svg?background=%23FFFFFF&axis=%23333333&line=%23e76060)](https://starchart.cc/niumoo/JavaNotes) - - ## ⏳ Java 开发 - [如何破解滑动验证码?](https://www.wdbyte.com/java/img-verification/) @@ -269,6 +264,11 @@ Java 版本任你发,我用 Java 8 。但是多学点这种装x技巧总没错 3. 我没有涉及到的知识点,也可以进行补充。 +## 赏个 Star + +[![Stargazers over time](https://starchart.cc/niumoo/JavaNotes.svg?background=%23FFFFFF&axis=%23333333&line=%23e76060)](https://starchart.cc/niumoo/JavaNotes) + + ### 公众号 可以关注「 **程序猿阿朗** 」公众号。即使查看更新的文章以及分享的干货。 From cf38764c543f10595ba9a4509e9f2f758ca58015 Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 2 Apr 2024 14:04:34 +0800 Subject: [PATCH 099/105] =?UTF-8?q?project:=20=E5=A2=9E=E5=8A=A0=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.mvn/wrapper/maven-wrapper.jar | Bin 0 -> 62547 bytes .../.mvn/wrapper/maven-wrapper.properties | 2 + .../springboot-weixin-qrcode-login/README.md | 1 + .../springboot-weixin-qrcode-login/mvnw | 308 ++++++++++++++++++ .../springboot-weixin-qrcode-login/mvnw.cmd | 205 ++++++++++++ .../springboot-weixin-qrcode-login/pom.xml | 98 ++++++ .../java/com/wdbyte/weixin/SpringBootApp.java | 18 + .../com/wdbyte/weixin/config/JwtFilter.java | 94 ++++++ .../controller/WeixinServerController.java | 53 +++ .../controller/WeixinUserController.java | 57 ++++ .../com/wdbyte/weixin/model/ApiResult.java | 32 ++ .../wdbyte/weixin/model/ReceiveMessage.java | 58 ++++ .../com/wdbyte/weixin/model/WeixinQrCode.java | 16 + .../weixin/service/WeixinUserService.java | 13 + .../service/impl/WeixinUserServiceImpl.java | 73 +++++ .../java/com/wdbyte/weixin/util/AesUtils.java | 62 ++++ .../com/wdbyte/weixin/util/ApiResultUtil.java | 30 ++ .../java/com/wdbyte/weixin/util/HttpUtil.java | 44 +++ .../java/com/wdbyte/weixin/util/JwtUtil.java | 88 +++++ .../java/com/wdbyte/weixin/util/KeyUtils.java | 25 ++ .../com/wdbyte/weixin/util/WeixinApiUtil.java | 74 +++++ .../com/wdbyte/weixin/util/WeixinMsgUtil.java | 68 ++++ .../weixin/util/WeixinQrCodeCacheUtil.java | 35 ++ .../java/com/wdbyte/weixin/util/XmlUtil.java | 32 ++ .../src/main/resources/application.properties | 6 + 25 files changed, 1492 insertions(+) create mode 100644 springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar create mode 100644 springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties create mode 100644 springboot/springboot-weixin-qrcode-login/README.md create mode 100755 springboot/springboot-weixin-qrcode-login/mvnw create mode 100644 springboot/springboot-weixin-qrcode-login/mvnw.cmd create mode 100644 springboot/springboot-weixin-qrcode-login/pom.xml create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java create mode 100644 springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties diff --git a/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..cb28b0e37c7d206feb564310fdeec0927af4123a GIT binary patch literal 62547 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 A?*IS* literal 0 HcmV?d00001 diff --git a/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/springboot/springboot-weixin-qrcode-login/README.md b/springboot/springboot-weixin-qrcode-login/README.md new file mode 100644 index 0000000..30d0f64 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/README.md @@ -0,0 +1 @@ +# Srping Boot 微信扫码登录示例 diff --git a/springboot/springboot-weixin-qrcode-login/mvnw b/springboot/springboot-weixin-qrcode-login/mvnw new file mode 100755 index 0000000..66df285 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/mvnw @@ -0,0 +1,308 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.2.0 +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /usr/local/etc/mavenrc ] ; then + . /usr/local/etc/mavenrc + fi + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "$(uname)" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME + else + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=$(java-config --jre-home) + fi +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then + if $darwin ; then + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" + else + javaExecutable="$(readlink -f "\"$javaExecutable\"")" + fi + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=$(cd "$wdir/.." || exit 1; pwd) + fi + # end of workaround + done + printf '%s' "$(cd "$basedir" || exit 1; pwd)" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" + fi +} + +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" +else + log "Couldn't find $wrapperJarPath, downloading it ..." + + if [ -n "$MVNW_REPOURL" ]; then + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + else + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + fi + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; + esac + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + + if $cygwin; then + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") + fi + + if command -v wget > /dev/null; then + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + else + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + else + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" + fi + else + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") + fi + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") + fi + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi +fi + +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$JAVA_HOME" ] && + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") + [ -n "$CLASSPATH" ] && + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +# shellcheck disable=SC2086 # safe args +exec "$JAVACMD" \ + $MAVEN_OPTS \ + $MAVEN_DEBUG_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/springboot/springboot-weixin-qrcode-login/mvnw.cmd b/springboot/springboot-weixin-qrcode-login/mvnw.cmd new file mode 100644 index 0000000..95ba6f5 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/mvnw.cmd @@ -0,0 +1,205 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.2.0 +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* +if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %WRAPPER_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% ^ + %JVM_CONFIG_MAVEN_PROPS% ^ + %MAVEN_OPTS% ^ + %MAVEN_DEBUG_OPTS% ^ + -classpath %WRAPPER_JAR% ^ + "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ + %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" +if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%"=="on" pause + +if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% + +cmd /C exit /B %ERROR_CODE% diff --git a/springboot/springboot-weixin-qrcode-login/pom.xml b/springboot/springboot-weixin-qrcode-login/pom.xml new file mode 100644 index 0000000..4c1a316 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.3 + + + com.wdbyte + springboot-weixin-qrcode-login + 0.0.1-SNAPSHOT + springboot-weixin-qrcode-login + Demo project for Spring Boot + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + true + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.15.2 + + + + org.apache.commons + commons-lang3 + 3.14.0 + + + + commons-codec + commons-codec + 1.16.0 + + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.1.3 + + + + org.apache.httpcomponents.client5 + httpclient5-fluent + 5.1.3 + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.45 + + + + com.google.guava + guava + 33.0.0-jre + + + + + com.auth0 + java-jwt + 4.4.0 + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java new file mode 100644 index 0000000..ca1c5dd --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java @@ -0,0 +1,18 @@ +package com.wdbyte.weixin; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.web.servlet.ServletComponentScan; + +/** + * @author https://www.wdbyte.com + */ +@SpringBootApplication +@ServletComponentScan(basePackages = "com.wdbyte.weixin.config") +public class SpringBootApp { + + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java new file mode 100644 index 0000000..1bce112 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java @@ -0,0 +1,94 @@ +package com.wdbyte.weixin.config; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.auth0.jwt.interfaces.Claim; +import com.wdbyte.weixin.util.ApiResultUtil; +import com.wdbyte.weixin.util.JwtUtil; +import jakarta.servlet.Filter; +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; + +/** + * JWT过滤器,拦截 /api/* 请求 + */ +@Slf4j +@WebFilter(filterName = "JwtFilter", urlPatterns = "/api/*") +public class JwtFilter implements Filter { + private static List EXCLUDE_PATH_LIST = new ArrayList<>(); + + static { + EXCLUDE_PATH_LIST.add("/api/v1/user/qrcode"); + EXCLUDE_PATH_LIST.add("/api/v1/user/code"); + EXCLUDE_PATH_LIST.add("/api/v1/user/login/code"); + EXCLUDE_PATH_LIST.add("/api/v1/user/login/qrcode"); + EXCLUDE_PATH_LIST.add("/api/v1/weixin/check"); + EXCLUDE_PATH_LIST.add("/api/v1/github/webhooks"); + } + + private JwtUtil jwtUtil; + + public JwtFilter(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) + throws IOException, ServletException { + + final HttpServletRequest request = (HttpServletRequest)req; + final HttpServletResponse response = (HttpServletResponse)res; + log.info("session id:{}", request.getRequestedSessionId()); + + response.setCharacterEncoding("UTF-8"); + String path = request.getRequestURI(); + for (String excludePath : EXCLUDE_PATH_LIST) { + if (excludePath.startsWith(path)) { + chain.doFilter(request, response); + return; + } + } + // 除OPTIONS外,其他请求应由JWT进行检查 + if ("OPTIONS".equals(request.getMethod())) { + response.setStatus(HttpServletResponse.SC_OK); + chain.doFilter(request, response); + return; + } + + //获取请求头里的token + String authorization = request.getHeader("Authorization"); + if (authorization == null || !StringUtils.startsWith(authorization, "Bearer ")) { + response.getWriter().write(ApiResultUtil.error("authentication failed")); + return; + } + authorization = StringUtils.replaceOnce(authorization, "Bearer ", StringUtils.EMPTY); + Map userData = jwtUtil.verifyToken(authorization); + if (userData == null) { + response.getWriter().write(ApiResultUtil.error("authentication failed")); + return; + } + String openId = userData.get(JwtUtil.OPEN_ID).asString(); + //拦截器 拿到用户信息,放到request中 + request.setAttribute(JwtUtil.OPEN_ID, openId); + chain.doFilter(req, res); + } + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java new file mode 100644 index 0000000..b3ff9dd --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java @@ -0,0 +1,53 @@ +package com.wdbyte.weixin.controller; + +import com.wdbyte.weixin.service.WeixinUserService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author https://www.wdbyte.com + * @date 2024/01/16 + */ +@Slf4j +@RestController +public class WeixinServerController { + + @Autowired + private WeixinUserService weixinUserService; + + @GetMapping(value = "/api/v1/weixin/check") + public String weixinCheck(HttpServletRequest request) { + String signature = request.getParameter("signature"); + String timestamp = request.getParameter("timestamp"); + String nonce = request.getParameter("nonce"); + String echostr = request.getParameter("echostr"); + + if (StringUtils.isEmpty(signature) || StringUtils.isEmpty(timestamp) || StringUtils.isEmpty(nonce)) { + return ""; + } + weixinUserService.checkSignature(signature, timestamp, nonce); + return echostr; + } + + @PostMapping(value = "/api/v1/weixin/check") + public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature, + @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { + + log.debug("requestBody:{}", requestBody); + log.debug("signature:{}", signature); + log.debug("timestamp:{}", timestamp); + log.debug("nonce:{}", nonce); + + weixinUserService.checkSignature(signature, timestamp, nonce); + return weixinUserService.handleWeixinMsg(requestBody); + } + + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java new file mode 100644 index 0000000..658b585 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java @@ -0,0 +1,57 @@ +package com.wdbyte.weixin.controller; + +import com.wdbyte.weixin.model.WeixinQrCode; +import com.wdbyte.weixin.util.ApiResultUtil; +import com.wdbyte.weixin.util.JwtUtil; +import com.wdbyte.weixin.util.WeixinApiUtil; +import com.wdbyte.weixin.util.WeixinQrCodeCacheUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author https://www.wdbyte.com + * @date 2024/03/16 + */ +@CrossOrigin(origins = {"https://www.wdbyte.com", "https://bing.wdbyte.com", "http://localhost:8000"}) +@Slf4j +@RestController +public class WeixinUserController { + + @Autowired + private WeixinApiUtil weixinApiUtil; + + @Autowired + private JwtUtil jwtUtil; + + @GetMapping(value = "/api/v1/user/qrcode") + public String getQrCode() { + WeixinQrCode qrCode = weixinApiUtil.getQrCode(); + qrCode.setUrl(null); + qrCode.setExpireSeconds(null); + return ApiResultUtil.success(qrCode); + } + + /** + * 校验是否扫描完成 + * 完成,返回 JWT + * 未完成,返回 check faild + * + * @param ticket + * @return + */ + @GetMapping(value = "/api/v1/user/login/qrcode") + public String userLogin(String ticket) { + String openId = WeixinQrCodeCacheUtil.get(ticket); + if (StringUtils.isNotEmpty(openId)) { + log.info("login success,open id:{}", openId); + return ApiResultUtil.success(jwtUtil.createToken(openId)); + } + log.info("login error,ticket:{}", ticket); + return ApiResultUtil.error("check faild"); + } + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java new file mode 100644 index 0000000..54b73e8 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java @@ -0,0 +1,32 @@ +package com.wdbyte.weixin.model; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +/** + * @author https://www.wdbyte.com + * @date 2022/01/04 + */ +@Slf4j +@Getter +@Setter +public class ApiResult { + private Integer code; + private String message; + private Object data; + + public ApiResult() { + } + + public ApiResult(Integer code, String message) { + this.code = code; + this.message = message; + } + + public ApiResult(Integer code, String message, Object data) { + this.code = code; + this.message = message; + this.data = data; + } +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java new file mode 100644 index 0000000..38f8d13 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ReceiveMessage.java @@ -0,0 +1,58 @@ +package com.wdbyte.weixin.model; + +import lombok.Data; + +@Data +public class ReceiveMessage { + /** + * 开发者微信号 + */ + private String toUserName; + /** + * 发送方账号(一个openid) + */ + private String fromUserName; + /** + * 消息创建时间(整形) + */ + private String createTime; + /** + * 消息类型 + */ + private String msgType; + /** + * 文本消息内容 + */ + private String content; + /** + * 消息ID 64位 + */ + String msgId; + /** + * 消息的数据ID 消息来自文章才有 + */ + private String msgDataId; + /** + * 多图文时第几篇文章,从1开始 消息如果来自文章才有 + */ + private String idx; + /** + * 订阅事件 subscribe 订阅 unsbscribe 取消订阅 + */ + private String event; + /** + * 扫码 - ticket + */ + private String ticket; + + public String getReplyTextMsg(String msg) { + String xml = "\n" + + " \n" + + " \n" + + " " + System.currentTimeMillis() + "\n" + + " \n" + + " \n" + + " "; + return xml; + } +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java new file mode 100644 index 0000000..b7dac86 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java @@ -0,0 +1,16 @@ +package com.wdbyte.weixin.model; + +import lombok.Data; + +/** + * @author https://www.wdbyte.com + * @date 2024/03/15 + */ +@Data +public class WeixinQrCode { + + private String ticket; + private Long expireSeconds; + private String url; + private String qrCodeUrl; +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java new file mode 100644 index 0000000..f81123a --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java @@ -0,0 +1,13 @@ +package com.wdbyte.weixin.service; + +/** + * @author niulang + * @date 2024/03/24 + */ +public interface WeixinUserService { + + void checkSignature(String signature, String timestamp, String nonce); + + String handleWeixinMsg(String body); + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java new file mode 100644 index 0000000..cce6eda --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java @@ -0,0 +1,73 @@ +package com.wdbyte.weixin.service.impl; + +import java.util.Arrays; + +import com.wdbyte.weixin.model.ReceiveMessage; +import com.wdbyte.weixin.service.WeixinUserService; +import com.wdbyte.weixin.util.WeixinMsgUtil; +import com.wdbyte.weixin.util.WeixinQrCodeCacheUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.codec.digest.DigestUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +/** + * @author niulang + * @date 2024/03/24 + */ +@Slf4j +@Service +public class WeixinUserServiceImpl implements WeixinUserService { + + private String token; + + public WeixinUserServiceImpl(@Value("${weixin.token}") String token) { + this.token = token; + } + + @Override + public void checkSignature(String signature, String timestamp, String nonce) { + String[] arr = new String[] {token, timestamp, nonce}; + Arrays.sort(arr); + StringBuilder content = new StringBuilder(); + for (String str : arr) { + content.append(str); + } + String tmpStr = DigestUtils.sha1Hex(content.toString()); + if (tmpStr.equals(signature)) { + log.info("check success"); + return; + } + log.error("check fail"); + throw new RuntimeException("check fail"); + } + + @Override + public String handleWeixinMsg(String requestBody) { + ReceiveMessage receiveMessage = WeixinMsgUtil.msgToReceiveMessage(requestBody); + // 扫码登录 + if (WeixinMsgUtil.isScanQrCode(receiveMessage)) { + return handleScanLogin(receiveMessage); + } + // 关注 + if (WeixinMsgUtil.isEventAndSubscribe(receiveMessage)) { + return receiveMessage.getReplyTextMsg("欢迎关注【程序猿阿朗】公众号"); + } + return receiveMessage.getReplyTextMsg("收到(自动回复)"); + } + + /** + * 处理扫码登录 + * + * @param receiveMessage + * @return + */ + private String handleScanLogin(ReceiveMessage receiveMessage) { + String qrCodeTicket = WeixinMsgUtil.getQrCodeTicket(receiveMessage); + if (WeixinQrCodeCacheUtil.get(qrCodeTicket) == null) { + String openId = receiveMessage.getFromUserName(); + WeixinQrCodeCacheUtil.put(qrCodeTicket, openId); + } + return receiveMessage.getReplyTextMsg("你已成功登录!"); + } +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java new file mode 100644 index 0000000..62ecd3a --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java @@ -0,0 +1,62 @@ +package com.wdbyte.weixin.util; + +import java.util.Base64; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; + +import lombok.SneakyThrows; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Java 使用 AES 加密算法进行加密解密 + * */ +@Component +public class AesUtils { + + /** + * 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥,否则就会遇到java.security.InvalidKeyException异常) + */ + @Value("${ase.util.secret}") + public String key; + + /** + * AES算法加密 + * @Param:text原文 + * @Param:key密钥 + * */ + + @SneakyThrows + public String aesEncrypt(String text) { + // 创建AES加密算法实例(根据传入指定的秘钥进行加密) + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + // 初始化为加密模式,并将密钥注入到算法中 + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + // 将传入的文本加密 + byte[] encrypted = cipher.doFinal(text.getBytes()); + //生成密文 + // 将密文进行Base64编码,方便传输 + return Base64.getEncoder().encodeToString(encrypted); + } + + /** + * AES算法解密 + * @Param:base64Encrypted密文 + * @Param:key密钥 + * */ + public String aesDecrypt(String base64Encrypted) throws Exception { + // 创建AES解密算法实例 + Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + + // 初始化为解密模式,并将密钥注入到算法中 + cipher.init(Cipher.DECRYPT_MODE, keySpec); + // 将Base64编码的密文解码 + byte[] encrypted = Base64.getDecoder().decode(base64Encrypted); + // 解密 + byte[] decrypted = cipher.doFinal(encrypted); + return new String(decrypted); + } +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java new file mode 100644 index 0000000..e2ad247 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java @@ -0,0 +1,30 @@ +package com.wdbyte.weixin.util; + +import com.alibaba.fastjson2.JSON; + +import com.wdbyte.weixin.model.ApiResult; + +/** + * @author https://www.wdbyte.com + * @date 2023/11/22 + */ +public class ApiResultUtil { + + private static Integer SUCCESS_CODE = 200; + private static String SUCCESS_MESSAGE = "success"; + private static Integer ERROR_CODE = -1; + private static String ERROR_MESSAGE = "error"; + + public static String success() { + return JSON.toJSONString(new ApiResult(SUCCESS_CODE, SUCCESS_MESSAGE, new Object())); + } + + public static String success(Object data) { + return JSON.toJSONString(new ApiResult(SUCCESS_CODE, SUCCESS_MESSAGE, data)); + } + + public static String error(Object data) { + return JSON.toJSONString(new ApiResult(ERROR_CODE, ERROR_MESSAGE, data)); + } + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java new file mode 100644 index 0000000..d536530 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/HttpUtil.java @@ -0,0 +1,44 @@ +package com.wdbyte.weixin.util; + +import java.io.IOException; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.fluent.Request; +import org.apache.hc.client5.http.fluent.Response; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.ParseException; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; + +public class HttpUtil { + + public static String get(String url) { + String result = null; + try { + Response response = Request.get(url).execute(); + result = response.returnContent().asString(); + } catch (IOException e) { + e.printStackTrace(); + } + return result; + } + + public static String post(String url, String jsonBody) { + String result = null; + HttpPost httpPost = new HttpPost(url); + httpPost.setEntity(new StringEntity(jsonBody, ContentType.APPLICATION_JSON)); + try (CloseableHttpClient httpclient = HttpClients.createDefault()) { + try (CloseableHttpResponse response = httpclient.execute(httpPost)) { + // 获取响应信息 + result = EntityUtils.toString(response.getEntity()); + } + } catch (IOException | ParseException e) { + e.printStackTrace(); + } + return result; + } + +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java new file mode 100644 index 0000000..581a0a3 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/JwtUtil.java @@ -0,0 +1,88 @@ +package com.wdbyte.weixin.util; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.TokenExpiredException; +import com.auth0.jwt.interfaces.Claim; +import com.auth0.jwt.interfaces.DecodedJWT; +import jakarta.annotation.PostConstruct; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Jwt工具类,生成JWT和认证 + */ +@Slf4j +@Component +public class JwtUtil { + + @Autowired + private AesUtils aesUtils; + + @Value("${key.jwt.secret}") + public String secret; + + private JWTVerifier verifier; + + @PostConstruct + public synchronized void init() { + log.info("init jwt verifier"); + verifier = JWT.require(Algorithm.HMAC256(secret)).build(); + } + + /** + * 过期时间,单位为秒,3天 + **/ + private static final long EXPIRATION = 3 * 24 * 3600L; + /** + * open id + */ + public static final String OPEN_ID = "oid"; + + /** + * 生成用户token,设置token超时时间 + */ + public String createToken(String openId) { + Date expireDate = new Date(System.currentTimeMillis() + EXPIRATION * 1000); + Map map = new HashMap<>(); + map.put("alg", "HS256"); + map.put("typ", "JWT"); + String token = JWT.create() + // 添加头部 + .withHeader(map) + // 可以将基本信息放到claims中 + .withClaim(OPEN_ID, openId) + //超时设置,设置过期的日期 + .withExpiresAt(expireDate) + //签发时间 + .withIssuedAt(new Date()) + //SECRET加密 + .sign(Algorithm.HMAC256(secret)); + return aesUtils.aesEncrypt(token); + } + + /** + * 校验token并解析token + */ + public Map verifyToken(String token) { + try { + token = aesUtils.aesDecrypt(token); + DecodedJWT jwt = verifier.verify(token); + return jwt.getClaims(); + } catch (Exception e) { + log.error(String.format("verifyToken error,token:%s,msg:%s", token, e.getMessage()), e); + if (e instanceof TokenExpiredException) { + throw new RuntimeException("jwt verify token error"); + } + } + return null; + } + +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java new file mode 100644 index 0000000..983b5b4 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java @@ -0,0 +1,25 @@ +package com.wdbyte.weixin.util; + +import java.util.UUID; + +import org.apache.commons.lang3.RandomStringUtils; + +/** + * @author https://www.wdbyte.com + * @date 2023/11/23 + */ +public class KeyUtils { + + public synchronized static String key6() { + return RandomStringUtils.randomAlphanumeric(6); + } + + public synchronized static String key16() { + return RandomStringUtils.randomAlphanumeric(16); + } + + public static String uuid32() { + return UUID.randomUUID().toString().replace("-", ""); + } + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java new file mode 100644 index 0000000..562648a --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java @@ -0,0 +1,74 @@ +package com.wdbyte.weixin.util; + +import java.net.URI; +import java.time.LocalDateTime; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import com.wdbyte.weixin.model.WeixinQrCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * @author https://www.wdbyte.com + * @date 2024/01/16 + */ +@Slf4j +@Component +public class WeixinApiUtil { + + @Value("${weixin.appid}") + public String appId; + + @Value("${weixin.appsecret}") + public String appSecret; + + private static String QR_CODE_URL_PREFIX = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="; + + private static String ACCESS_TOKEN = null; + private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null; + + /** + * 获取唯一 access token + * + * @return + */ + public synchronized String getAccessToken() { + if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) { + return ACCESS_TOKEN; + } + String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; + String result = HttpUtil.get(api); + JSONObject jsonObject = JSON.parseObject(result); + ACCESS_TOKEN = jsonObject.getString("access_token"); + ACCESS_TOKEN_EXPIRE_TIME = LocalDateTime.now().plusSeconds(jsonObject.getLong("expires_in") - 10); + return ACCESS_TOKEN; + } + + /** + * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html + * + * @return + */ + public WeixinQrCode getQrCode() { + String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken(); + int timeout = 10 * 60 ; + String jsonBody = String.format("{\n" + + " \"expire_seconds\": %d,\n" + + " \"action_name\": \"QR_STR_SCENE\",\n" + + " \"action_info\": {\n" + + " \"scene\": {\n" + + " \"scene_str\": \"%s\"\n" + + " }\n" + + " }\n" + + "}", timeout, KeyUtils.uuid32()); + String result = HttpUtil.post(api, jsonBody); + log.info("get qr code params:{}", jsonBody); + log.info("get qr code result:{}", result); + WeixinQrCode weixinQrCode = JSON.parseObject(result, WeixinQrCode.class); + weixinQrCode.setQrCodeUrl(QR_CODE_URL_PREFIX + URI.create(weixinQrCode.getTicket()).toASCIIString()); + return weixinQrCode; + } +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java new file mode 100644 index 0000000..2554d62 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java @@ -0,0 +1,68 @@ +package com.wdbyte.weixin.util; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; + +import com.wdbyte.weixin.model.ReceiveMessage; +import org.apache.commons.lang3.StringUtils; + +/** + * @author https://www.wdbyte.com + * @date 2024/01/21 + */ +public class WeixinMsgUtil { + + // 事件-关注 + private static String EVENT_SUBSCRIBE = "subscribe"; + + /** + * 微信消息转对象 + * + * @param xml + * @return + */ + public static ReceiveMessage msgToReceiveMessage(String xml) { + JSONObject jsonObject = JSON.parseObject(XmlUtil.xml2json(xml)); + ReceiveMessage receiveMessage = new ReceiveMessage(); + receiveMessage.setToUserName(jsonObject.getString("ToUserName")); + receiveMessage.setFromUserName(jsonObject.getString("FromUserName")); + receiveMessage.setCreateTime(jsonObject.getString("CreateTime")); + receiveMessage.setMsgType(jsonObject.getString("MsgType")); + receiveMessage.setContent(jsonObject.getString("Content")); + receiveMessage.setMsgId(jsonObject.getString("MsgId")); + receiveMessage.setEvent(jsonObject.getString("Event")); + receiveMessage.setTicket(jsonObject.getString("Ticket")); + return receiveMessage; + } + + /** + * 是否是订阅事件 + * + * @param receiveMessage + * @return + */ + public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) { + return StringUtils.equals(receiveMessage.getEvent(),EVENT_SUBSCRIBE); + } + + /** + * 是否是二维码扫描事件 + * + * @param receiveMessage + * @return + */ + public static boolean isScanQrCode(ReceiveMessage receiveMessage) { + return StringUtils.isNotEmpty(receiveMessage.getTicket()); + } + + /** + * 获取扫描的二维码 Ticket + * + * @param receiveMessage + * @return + */ + public static String getQrCodeTicket(ReceiveMessage receiveMessage) { + return receiveMessage.getTicket(); + } + +} \ No newline at end of file diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java new file mode 100644 index 0000000..a69c56e --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java @@ -0,0 +1,35 @@ +package com.wdbyte.weixin.util; + +import java.util.LinkedHashMap; + +/** + * 微信二维码缓存工具类 + * + * @author https://www.wdbyte.com + * @date 2024/03/16 + */ +public class WeixinQrCodeCacheUtil { + private static long MAX_CACHE_SIZE = 10000; + private static LinkedHashMap QR_CODE_TICKET_MAP = new LinkedHashMap<>(); + + /** + * 增加一个 Ticket + * 首次 put:value 为 "" + * 再次 put: value 有 openId,若openId已经存在,则已被扫码 + * + * @param key + * @param value + */ + public synchronized static void put(String key, String value) { + QR_CODE_TICKET_MAP.put(key, value); + if (QR_CODE_TICKET_MAP.size() > MAX_CACHE_SIZE) { + String first = QR_CODE_TICKET_MAP.keySet().stream().findFirst().get(); + QR_CODE_TICKET_MAP.remove(first); + } + } + + public synchronized static String get(String key) { + return QR_CODE_TICKET_MAP.remove(key); + } + +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java new file mode 100644 index 0000000..e8c4dea --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java @@ -0,0 +1,32 @@ +package com.wdbyte.weixin.util; + +import com.alibaba.fastjson2.JSON; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.web.bind.annotation.RequestBody; + +/** + * @author https://www.wdbyte.com + * @date 2024/01/18 + */ +@Slf4j +public class XmlUtil { + + public static String xml2json(String requestBody) { + requestBody = StringUtils.trim(requestBody); + XmlMapper xmlMapper = new XmlMapper(); + JsonNode node = null; + try { + node = xmlMapper.readTree(requestBody.getBytes()); + ObjectMapper jsonMapper = new ObjectMapper(); + return jsonMapper.writeValueAsString(node); + } catch (Exception e) { + log.error("xml 2 json error,msg:" + e.getMessage(), e); + } + return null; + } +} diff --git a/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties b/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties new file mode 100644 index 0000000..453f434 --- /dev/null +++ b/springboot/springboot-weixin-qrcode-login/src/main/resources/application.properties @@ -0,0 +1,6 @@ +server.port= +weixin.appid= +weixin.appsecret= +weixin.token= +ase.util.secret= +key.jwt.secret= \ No newline at end of file From a9d4766e102b989b65a880068c5cc9b6991ce756 Mon Sep 17 00:00:00 2001 From: niumoo Date: Tue, 2 Apr 2024 15:59:27 +0800 Subject: [PATCH 100/105] =?UTF-8?q?project:=20=E5=A2=9E=E5=8A=A0=E5=BE=AE?= =?UTF-8?q?=E4=BF=A1=E6=89=AB=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/wdbyte/weixin/SpringBootApp.java | 6 +++--- .../java/com/wdbyte/weixin/config/JwtFilter.java | 13 +++++-------- .../weixin/controller/WeixinServerController.java | 8 +++----- .../weixin/controller/WeixinUserController.java | 5 ++--- .../java/com/wdbyte/weixin/model/ApiResult.java | 1 - .../com/wdbyte/weixin/model/WeixinQrCode.java | 1 - .../wdbyte/weixin/service/WeixinUserService.java | 3 +-- .../service/impl/WeixinUserServiceImpl.java | 8 ++------ .../java/com/wdbyte/weixin/util/AesUtils.java | 12 +++++++----- .../com/wdbyte/weixin/util/ApiResultUtil.java | 1 - .../java/com/wdbyte/weixin/util/KeyUtils.java | 1 - .../com/wdbyte/weixin/util/WeixinApiUtil.java | 15 ++++++++++----- .../com/wdbyte/weixin/util/WeixinMsgUtil.java | 3 +-- .../wdbyte/weixin/util/WeixinQrCodeCacheUtil.java | 1 - .../main/java/com/wdbyte/weixin/util/XmlUtil.java | 4 ---- 15 files changed, 34 insertions(+), 48 deletions(-) diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java index ca1c5dd..5720502 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/SpringBootApp.java @@ -11,8 +11,8 @@ @ServletComponentScan(basePackages = "com.wdbyte.weixin.config") public class SpringBootApp { - public static void main(String[] args) { - SpringApplication.run(SpringBootApp.class, args); - } + public static void main(String[] args) { + SpringApplication.run(SpringBootApp.class, args); + } } diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java index 1bce112..03de5c5 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/config/JwtFilter.java @@ -21,20 +21,17 @@ import org.apache.commons.lang3.StringUtils; /** - * JWT过滤器,拦截 /api/* 请求 + * JWT过滤器,拦截 /user/* 请求 */ @Slf4j -@WebFilter(filterName = "JwtFilter", urlPatterns = "/api/*") +@WebFilter(filterName = "JwtFilter", urlPatterns = {"/user/*"}) public class JwtFilter implements Filter { private static List EXCLUDE_PATH_LIST = new ArrayList<>(); static { - EXCLUDE_PATH_LIST.add("/api/v1/user/qrcode"); - EXCLUDE_PATH_LIST.add("/api/v1/user/code"); - EXCLUDE_PATH_LIST.add("/api/v1/user/login/code"); - EXCLUDE_PATH_LIST.add("/api/v1/user/login/qrcode"); - EXCLUDE_PATH_LIST.add("/api/v1/weixin/check"); - EXCLUDE_PATH_LIST.add("/api/v1/github/webhooks"); + EXCLUDE_PATH_LIST.add("/user/qrcode"); + EXCLUDE_PATH_LIST.add("/user/login/qrcode"); + EXCLUDE_PATH_LIST.add("/weixin/check"); } private JwtUtil jwtUtil; diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java index b3ff9dd..95740bc 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinServerController.java @@ -13,7 +13,6 @@ /** * @author https://www.wdbyte.com - * @date 2024/01/16 */ @Slf4j @RestController @@ -22,7 +21,7 @@ public class WeixinServerController { @Autowired private WeixinUserService weixinUserService; - @GetMapping(value = "/api/v1/weixin/check") + @GetMapping(value = "/weixin/check") public String weixinCheck(HttpServletRequest request) { String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); @@ -36,9 +35,9 @@ public String weixinCheck(HttpServletRequest request) { return echostr; } - @PostMapping(value = "/api/v1/weixin/check") + @PostMapping(value = "/weixin/check") public String weixinMsg(@RequestBody String requestBody, @RequestParam("signature") String signature, - @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { + @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) { log.debug("requestBody:{}", requestBody); log.debug("signature:{}", signature); @@ -49,5 +48,4 @@ public String weixinMsg(@RequestBody String requestBody, @RequestParam("signatur return weixinUserService.handleWeixinMsg(requestBody); } - } diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java index 658b585..074022d 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/controller/WeixinUserController.java @@ -14,7 +14,6 @@ /** * @author https://www.wdbyte.com - * @date 2024/03/16 */ @CrossOrigin(origins = {"https://www.wdbyte.com", "https://bing.wdbyte.com", "http://localhost:8000"}) @Slf4j @@ -27,7 +26,7 @@ public class WeixinUserController { @Autowired private JwtUtil jwtUtil; - @GetMapping(value = "/api/v1/user/qrcode") + @GetMapping(value = "/user/qrcode") public String getQrCode() { WeixinQrCode qrCode = weixinApiUtil.getQrCode(); qrCode.setUrl(null); @@ -43,7 +42,7 @@ public String getQrCode() { * @param ticket * @return */ - @GetMapping(value = "/api/v1/user/login/qrcode") + @GetMapping(value = "/user/login/qrcode") public String userLogin(String ticket) { String openId = WeixinQrCodeCacheUtil.get(ticket); if (StringUtils.isNotEmpty(openId)) { diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java index 54b73e8..83c4279 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/ApiResult.java @@ -6,7 +6,6 @@ /** * @author https://www.wdbyte.com - * @date 2022/01/04 */ @Slf4j @Getter diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java index b7dac86..66bdf65 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/model/WeixinQrCode.java @@ -4,7 +4,6 @@ /** * @author https://www.wdbyte.com - * @date 2024/03/15 */ @Data public class WeixinQrCode { diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java index f81123a..2d1cd71 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/WeixinUserService.java @@ -1,8 +1,7 @@ package com.wdbyte.weixin.service; /** - * @author niulang - * @date 2024/03/24 + * @Author 公众号:程序猿阿朗 */ public interface WeixinUserService { diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java index cce6eda..78939d0 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/service/impl/WeixinUserServiceImpl.java @@ -12,19 +12,15 @@ import org.springframework.stereotype.Service; /** - * @author niulang - * @date 2024/03/24 + * @Author 公众号:程序猿阿朗 */ @Slf4j @Service public class WeixinUserServiceImpl implements WeixinUserService { + @Value("${weixin.token}") private String token; - public WeixinUserServiceImpl(@Value("${weixin.token}") String token) { - this.token = token; - } - @Override public void checkSignature(String signature, String timestamp, String nonce) { String[] arr = new String[] {token, timestamp, nonce}; diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java index 62ecd3a..5c1cd39 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/AesUtils.java @@ -11,24 +11,25 @@ /** * Java 使用 AES 加密算法进行加密解密 - * */ + */ @Component public class AesUtils { /** - * 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥,否则就会遇到java.security.InvalidKeyException异常) + * 秘钥(需要使用长度为16、24或32的字节数组作为AES算法的密钥,否则就会遇到java.security.InvalidKeyException异常) */ @Value("${ase.util.secret}") public String key; /** * AES算法加密 + * * @Param:text原文 * @Param:key密钥 - * */ + */ @SneakyThrows - public String aesEncrypt(String text) { + public String aesEncrypt(String text) { // 创建AES加密算法实例(根据传入指定的秘钥进行加密) Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); @@ -43,9 +44,10 @@ public String aesEncrypt(String text) { /** * AES算法解密 + * * @Param:base64Encrypted密文 * @Param:key密钥 - * */ + */ public String aesDecrypt(String base64Encrypted) throws Exception { // 创建AES解密算法实例 Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java index e2ad247..d68f622 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/ApiResultUtil.java @@ -6,7 +6,6 @@ /** * @author https://www.wdbyte.com - * @date 2023/11/22 */ public class ApiResultUtil { diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java index 983b5b4..d56ebe0 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/KeyUtils.java @@ -6,7 +6,6 @@ /** * @author https://www.wdbyte.com - * @date 2023/11/23 */ public class KeyUtils { diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java index 562648a..6a3f8b0 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinApiUtil.java @@ -13,7 +13,6 @@ /** * @author https://www.wdbyte.com - * @date 2024/01/16 */ @Slf4j @Component @@ -29,9 +28,13 @@ public class WeixinApiUtil { private static String ACCESS_TOKEN = null; private static LocalDateTime ACCESS_TOKEN_EXPIRE_TIME = null; + /** + * 二维码 Ticket 过期时间 + */ + private static int QR_CODE_TICKET_TIMEOUT = 10 * 60; /** - * 获取唯一 access token + * 获取 access token * * @return */ @@ -39,7 +42,8 @@ public synchronized String getAccessToken() { if (ACCESS_TOKEN != null && ACCESS_TOKEN_EXPIRE_TIME.isAfter(LocalDateTime.now())) { return ACCESS_TOKEN; } - String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret; + String api = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + + appSecret; String result = HttpUtil.get(api); JSONObject jsonObject = JSON.parseObject(result); ACCESS_TOKEN = jsonObject.getString("access_token"); @@ -48,13 +52,14 @@ public synchronized String getAccessToken() { } /** + * 获取二维码 Ticket + * * https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html * * @return */ public WeixinQrCode getQrCode() { String api = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + getAccessToken(); - int timeout = 10 * 60 ; String jsonBody = String.format("{\n" + " \"expire_seconds\": %d,\n" + " \"action_name\": \"QR_STR_SCENE\",\n" @@ -63,7 +68,7 @@ public WeixinQrCode getQrCode() { + " \"scene_str\": \"%s\"\n" + " }\n" + " }\n" - + "}", timeout, KeyUtils.uuid32()); + + "}", QR_CODE_TICKET_TIMEOUT, KeyUtils.uuid32()); String result = HttpUtil.post(api, jsonBody); log.info("get qr code params:{}", jsonBody); log.info("get qr code result:{}", result); diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java index 2554d62..a48c0cb 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinMsgUtil.java @@ -8,7 +8,6 @@ /** * @author https://www.wdbyte.com - * @date 2024/01/21 */ public class WeixinMsgUtil { @@ -42,7 +41,7 @@ public static ReceiveMessage msgToReceiveMessage(String xml) { * @return */ public static boolean isEventAndSubscribe(ReceiveMessage receiveMessage) { - return StringUtils.equals(receiveMessage.getEvent(),EVENT_SUBSCRIBE); + return StringUtils.equals(receiveMessage.getEvent(), EVENT_SUBSCRIBE); } /** diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java index a69c56e..1e55d9e 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/WeixinQrCodeCacheUtil.java @@ -6,7 +6,6 @@ * 微信二维码缓存工具类 * * @author https://www.wdbyte.com - * @date 2024/03/16 */ public class WeixinQrCodeCacheUtil { private static long MAX_CACHE_SIZE = 10000; diff --git a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java index e8c4dea..bc56199 100644 --- a/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java +++ b/springboot/springboot-weixin-qrcode-login/src/main/java/com/wdbyte/weixin/util/XmlUtil.java @@ -1,17 +1,13 @@ package com.wdbyte.weixin.util; -import com.alibaba.fastjson2.JSON; - import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.web.bind.annotation.RequestBody; /** * @author https://www.wdbyte.com - * @date 2024/01/18 */ @Slf4j public class XmlUtil { From 34398bdd15d7af282daabf9a68c04b4a566379fd Mon Sep 17 00:00:00 2001 From: niumoo Date: Wed, 24 Apr 2024 09:39:15 +0800 Subject: [PATCH 101/105] =?UTF-8?q?feat:[Java=20=E6=96=AD=E8=A8=80=20Asser?= =?UTF-8?q?t=20=E4=BD=BF=E7=94=A8=E6=95=99=E7=A8=8B=E4=B8=8E=E6=9C=80?= =?UTF-8?q?=E4=BD=B3=E5=AE=9E=E8=B7=B5](https://www.wdbyte.com/java/assert?= =?UTF-8?q?/)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 + core-java-modules/core-java-base/README.md | 3 +- .../java/com/wdbyte/assert1/AssertDemo1.java | 29 ++++++++ .../java/com/wdbyte/assert1/AssertDemo2.java | 24 ++++++ .../java/com/wdbyte/assert1/AssertDemo3.java | 20 +++++ .../java/com/wdbyte/assert1/AssertDemo4.java | 25 +++++++ .../java/com/wdbyte/assert1/AssertDemo5.java | 25 +++++++ .../wdbyte/assert1/InitializationDemo.java | 28 +++++++ .../java/com/wdbyte/enum2/WeekdayTest.java | 1 - .../wdbyte/thread/CompletableFutureTest.java | 74 +++++++++++++++++++ 10 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java create mode 100644 core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java diff --git a/README.md b/README.md index 1ec435e..763ca75 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,9 @@ - [Java 集合框架](https://www.wdbyte.com/java/collection/) - [Java 中使用 List ](https://www.wdbyte.com/java/list/) +### 代码测试 +- [Java 断言 Assert 使用教程与最佳实践](https://www.wdbyte.com/java/assert/) + ## 😃Java I/O 教程 - [Java 创建和写入文件](https://www.wdbyte.com/java/io/file-create-write/) diff --git a/core-java-modules/core-java-base/README.md b/core-java-modules/core-java-base/README.md index 0071ce6..5b2d151 100644 --- a/core-java-modules/core-java-base/README.md +++ b/core-java-modules/core-java-base/README.md @@ -22,4 +22,5 @@ - [Java 枚举](https://www.wdbyte.com/java/enum/) - [Java 注释](*https://www.wdbyte.com/java/comment/*) - [Java 集合框架](https://www.wdbyte.com/java/collection/) -- [Java 中使用 List ](https://www.wdbyte.com/java/list/) \ No newline at end of file +- [Java 中使用 List ](https://www.wdbyte.com/java/list/) +- [Java 断言 Assert 使用教程与最佳实践](https://www.wdbyte.com/java/assert/) \ No newline at end of file diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java new file mode 100644 index 0000000..9d808cf --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java @@ -0,0 +1,29 @@ +package com.wdbyte.assert1; + +import java.util.Arrays; +import java.util.List; + +/** + * @author niulang + * @date 2024/04/22 + */ +public class AssertDemo1 { + public static void main(String[] args) { + + List list = Arrays.asList("1", "2"); + boolean result = list.remove("x"); + //assert result; + assert result : "移除失败"; + System.out.println(calc(100, 10)); + + // 手动开启断言 + //ClassLoader.getSystemClassLoader().setDefaultAssertionStatus(true); + //System.out.println(calc(100, 0)); + } + + public static int calc(int a, int b) { + assert b != 0 : "除数不能为0"; + return a / b; + + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java new file mode 100644 index 0000000..1cc5664 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java @@ -0,0 +1,24 @@ +package com.wdbyte.assert1; + +import java.util.Arrays; +import java.util.List; + +import static com.google.common.base.Verify.*; + +/** + * @author niulang + * @date 2024/04/22 + */ +public class AssertDemo2 { + public static void main(String[] args) { + int x = 100; + verifyNotNull(x != 0); + System.out.println(calc(100, 10)); + System.out.println(calc(100, 0)); + } + + public static int calc(int a, int b) { + verify(b != 0, "除数不能为0"); + return a / b; + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java new file mode 100644 index 0000000..3f02ca2 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java @@ -0,0 +1,20 @@ +package com.wdbyte.assert1; + +import java.util.Arrays; +import java.util.List; + +/** + * @author niulang + * @date 2024/04/22 + */ +public class AssertDemo3 { + static final boolean asserts = false; // 设置为 false 来消除断言 + + public static void main(String[] args) { + List list = Arrays.asList("1", "2"); + boolean result = list.remove("x"); + if (asserts) { + assert result : "移除失败"; + } + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java new file mode 100644 index 0000000..b324a0d --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java @@ -0,0 +1,25 @@ +package com.wdbyte.assert1; + +import java.util.Arrays; +import java.util.List; + +/** + * @author niulang + * @date 2024/04/22 + */ +public class AssertDemo4 { + + static { + boolean assertsEnabled = false; + assert assertsEnabled = true; // 故意产生副作用!!! + if (!assertsEnabled) { + throw new RuntimeException("必须启用断言!!!"); + } + } + + public static void main(String[] args) { + List list = Arrays.asList("1", "2"); + boolean result = list.remove("x"); + assert result : "移除失败"; + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java new file mode 100644 index 0000000..6d008c4 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java @@ -0,0 +1,25 @@ +package com.wdbyte.assert1; + +import java.util.Arrays; +import java.util.List; + +import com.google.common.base.Preconditions; +import com.google.common.base.Verify; +import org.apache.commons.lang3.Validate; +import org.junit.jupiter.api.Assertions; + +/** + * @author niulang + * @date 2024/04/22 + */ +public class AssertDemo5 { + + public static void main(String[] args) { + List list = Arrays.asList("1", "2"); + boolean result = list.remove("x"); + Assertions.assertTrue(result); + Preconditions.checkNotNull("","msg"); + Validate.isTrue(list.isEmpty(),"msg"); + Verify.verify(list.isEmpty(),"msg"); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java new file mode 100644 index 0000000..cfebe99 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/InitializationDemo.java @@ -0,0 +1,28 @@ +package com.wdbyte.assert1; + +public class InitializationDemo { + + static { + init(); + } + + static void init() { + System.out.println("Static initialization block called"); + // 假设这里有一个重要的初始化逻辑 + // 这个方法错误地在静态初始化之前被调用了 + assert isProperlyInitialized() : "System not properly initialized"; + } + + static boolean isProperlyInitialized() { + // 这里返回 false 模拟系统未被正确初始化 + return false; + } + + public InitializationDemo() { + System.out.println("Constructor called"); + } + + public static void main(String[] args) { + new InitializationDemo(); + } +} diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java index 6d83265..30e6fd8 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/enum2/WeekdayTest.java @@ -11,7 +11,6 @@ public static void main(String[] args) { System.out.println("Today is Monday."); } - Weekday[] weekdays = Weekday.values(); for (Weekday weekday : weekdays) { System.out.println(weekday); diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java new file mode 100644 index 0000000..af22eb6 --- /dev/null +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java @@ -0,0 +1,74 @@ +package com.wdbyte.thread; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; + +import org.junit.jupiter.api.Test; + +/** + * @author niulang + * @date 2023/11/01 + */ +public class CompletableFutureTest { + + /** + * 异步执行程序后,对正常响应和异常响应进行处理 + */ + @Test + public void completableFutureTest1() { + CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> { + sleep(2000); + System.out.println("do....."); + return 1; + }); + + completableFuture1.thenAccept(res -> { + System.out.println("收到结果:" + res + ); + }); + + System.out.println("等待"); + sleep(10 * 1000); + } + @Test + public void completableFutureTest2() { + CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> { + sleep(2000); + System.out.println("do2....."); + return 10 / 0; + }); + completableFuture2.exceptionally(except -> { + System.out.println("发生异常:" + except.getMessage()); + return 0; + }); + + System.out.println("等待"); + sleep(10 * 1000); + } + + @Test + public void completableFutureTest3() { + CompletableFuture completableFuture1 = CompletableFuture.supplyAsync(() -> { + sleep(2000); + System.out.println("do....."); + return 1; + }); + + completableFuture1.thenAccept(res -> { + System.out.println("收到结果:" + res + ); + }); + + System.out.println("等待"); + sleep(10 * 1000); + } + + void sleep(long millis){ + try { + Thread.sleep(millis); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + +} From 4fc2f5bf44978729c4ca4efd375a4dc43622d6f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E6=9C=97?= Date: Mon, 28 Apr 2025 16:24:04 +0800 Subject: [PATCH 102/105] code opt --- core-java-modules/core-java-22/pom.xml | 14 ++++ .../src/main/java/com/wdbyte/Main.java | 21 ++++++ core-java-modules/core-java-8/pom.xml | 5 -- .../src/main/java/com/wdbyte/Jdk8Lambda.java | 32 ++++++-- .../main/java/com/wdbyte/Jdk8Optional.java | 29 ++++++-- pom.xml | 5 -- .../src/main/java/com/wdbyte/jackson/Cat.java | 23 +++++- .../main/java/com/wdbyte/jackson/Order.java | 33 ++++++--- .../main/java/com/wdbyte/jackson/Person.java | 45 +++++++++++- .../main/java/com/wdbyte/jackson/Student.java | 73 ++++++++++++++----- 10 files changed, 220 insertions(+), 60 deletions(-) create mode 100644 core-java-modules/core-java-22/pom.xml create mode 100644 core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java diff --git a/core-java-modules/core-java-22/pom.xml b/core-java-modules/core-java-22/pom.xml new file mode 100644 index 0000000..4fc6401 --- /dev/null +++ b/core-java-modules/core-java-22/pom.xml @@ -0,0 +1,14 @@ + + + 4.0.0 + com.wdbyte.core-java-modules + core-java-22 + 1.0.0-SNAPSHOT + + 22 + 22 + UTF-8 + + \ No newline at end of file diff --git a/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java new file mode 100644 index 0000000..b7e6be5 --- /dev/null +++ b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java @@ -0,0 +1,21 @@ +package com.wdbyte; + +/** + * @author niulang + * @date 2025/04/28 + */ +//TIP To Run code, press or +// click the icon in the gutter. +public class Main { + public static void main(String[] args) { + //TIP Press with your caret at the highlighted text + // to see how IntelliJ IDEA suggests fixing it. + System.out.printf("Hello and welcome!"); + + for (int i = 1; i <= 5; i++) { + //TIP Press to start debugging your code. We have set one breakpoint + // for you, but you can always add more by pressing . + System.out.println("i = " + i); + } + } +} \ No newline at end of file diff --git a/core-java-modules/core-java-8/pom.xml b/core-java-modules/core-java-8/pom.xml index 2ae8b99..132cc2e 100644 --- a/core-java-modules/core-java-8/pom.xml +++ b/core-java-modules/core-java-8/pom.xml @@ -35,11 +35,6 @@ org.junit.jupiter junit-jupiter - - org.projectlombok - lombok - 1.18.22 - \ No newline at end of file diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java index 357f967..cbd5029 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Lambda.java @@ -1,9 +1,5 @@ package com.wdbyte; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; import org.junit.jupiter.api.Test; import java.util.*; @@ -70,13 +66,33 @@ public void functionLambdaTest() { } - @Getter - @Setter - @ToString - @AllArgsConstructor static class User { private String name; private Integer age; + + public User() { + } + + public User(String name, Integer age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } } public static List userList = new ArrayList(); diff --git a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java index ec3fa57..fc080a8 100644 --- a/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java +++ b/core-java-modules/core-java-8/src/main/java/com/wdbyte/Jdk8Optional.java @@ -2,8 +2,6 @@ import java.util.Optional; - -import lombok.Data; import org.junit.jupiter.api.Test; /** @@ -182,23 +180,44 @@ public void optionalTest() { /** * 计算机 */ -@Data class Computer { private Optional soundCard; + + public Optional getSoundCard() { + return soundCard; + } + + public void setSoundCard(Optional soundCard) { + this.soundCard = soundCard; + } } /** * 声卡 */ -@Data class SoundCard { private Optional usb; + + public Optional getUsb() { + return usb; + } + + public void setUsb(Optional usb) { + this.usb = usb; + } } /** * USB */ -@Data class Usb { private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } } diff --git a/pom.xml b/pom.xml index 0e2c7f6..b55b3df 100644 --- a/pom.xml +++ b/pom.xml @@ -45,10 +45,5 @@ commons-lang3 ${commons-lang3.version} - - org.projectlombok - lombok - ${lombok.version} - diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java index 5492a46..be544cd 100644 --- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Cat.java @@ -1,15 +1,12 @@ package com.wdbyte.jackson; import com.fasterxml.jackson.annotation.JsonGetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonSetter; -import lombok.Data; /** * @author https://www.wdbyte.com * @date 2022/07/17 */ -@Data public class Cat { @JsonSetter(value = "catName") @@ -21,4 +18,24 @@ public class Cat { public String getName() { return name; } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Cat() { + } + + public Cat(String name, Integer age) { + this.name = name; + this.age = age; + } } diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java index 6318eb3..38eb08d 100644 --- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Order.java @@ -1,24 +1,15 @@ package com.wdbyte.jackson; -import java.time.LocalDateTime; -import java.util.Date; - import com.fasterxml.jackson.annotation.JsonFormat; -import com.fasterxml.jackson.annotation.JsonGetter; import com.fasterxml.jackson.annotation.JsonSetter; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.ToString; + +import java.time.LocalDateTime; +import java.util.Date; /** * @author https://www.wdbyte.com * @date 2022/07/17 */ -//@Data -@AllArgsConstructor -@NoArgsConstructor -@ToString public class Order { @JsonSetter(value = "orderId") @@ -54,4 +45,22 @@ public LocalDateTime getUpdateTime() { public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; } + + @Override + public String toString() { + return "Order{" + + "id=" + id + + ", createTime=" + createTime + + ", updateTime=" + updateTime + + '}'; + } + + public Order() { + } + + public Order(Integer id, Date createTime, LocalDateTime updateTime) { + this.id = id; + this.createTime = createTime; + this.updateTime = updateTime; + } } diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java index 3bc1210..b1f2a72 100644 --- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Person.java @@ -2,16 +2,55 @@ import java.util.List; -import lombok.Data; - /** * @author https://www.wdbyte.com * @date 2022/07/16 */ -@Data public class Person { private String name; private Integer age; private List skillList; + + @Override + public String toString() { + return "Person{" + + "name='" + name + '\'' + + ", age=" + age + + ", skillList=" + skillList + + '}'; + } + + public Person(String name, Integer age, List skillList) { + this.name = name; + this.age = age; + this.skillList = skillList; + } + + public Person() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public List getSkillList() { + return skillList; + } + + public void setSkillList(List skillList) { + this.skillList = skillList; + } } diff --git a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java index 377253d..f5baec4 100644 --- a/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java +++ b/tool-java-jackson/src/main/java/com/wdbyte/jackson/Student.java @@ -1,36 +1,20 @@ package com.wdbyte.jackson; -import java.util.HashMap; -import java.util.Map; - import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.google.common.collect.Maps; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; + +import java.util.HashMap; +import java.util.Map; /** * @author https://www.wdbyte.com * @date 2022/07/17 */ -@ToString -@AllArgsConstructor -@NoArgsConstructor public class Student { - @Getter - @Setter private String name; - @Getter - @Setter private Integer age; - @Getter - @Setter private Map diyMap = new HashMap<>(); @JsonAnyGetter @@ -45,4 +29,55 @@ public void otherField(String key, String value) { this.diyMap.put(key, value); } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Map getDiyMap() { + return diyMap; + } + + public void setDiyMap(Map diyMap) { + this.diyMap = diyMap; + } + + public Map getInitMap() { + return initMap; + } + + public void setInitMap(Map initMap) { + this.initMap = initMap; + } + + public Student() { + } + + public Student(String name, Integer age, Map diyMap, Map initMap) { + this.name = name; + this.age = age; + this.diyMap = diyMap; + this.initMap = initMap; + } + + @Override + public String toString() { + return "Student{" + + "name='" + name + '\'' + + ", age=" + age + + ", diyMap=" + diyMap + + ", initMap=" + initMap + + '}'; + } } From 36ec1faf3fdbbc6bf21333ad6d49d2b144f00f0e Mon Sep 17 00:00:00 2001 From: niumoo Date: Mon, 28 Apr 2025 17:27:44 +0800 Subject: [PATCH 103/105] code opt --- .../core-java-22/src/main/java/com/wdbyte/Main.java | 2 +- .../src/main/java/com/wdbyte/assert1/AssertDemo1.java | 2 +- .../src/main/java/com/wdbyte/assert1/AssertDemo2.java | 2 +- .../src/main/java/com/wdbyte/assert1/AssertDemo3.java | 2 +- .../src/main/java/com/wdbyte/assert1/AssertDemo4.java | 2 +- .../src/main/java/com/wdbyte/assert1/AssertDemo5.java | 2 +- .../src/main/java/com/wdbyte/collection/ArrayListTest.java | 2 +- .../src/main/java/com/wdbyte/collection/ArrayListTest2.java | 2 +- .../src/main/java/com/wdbyte/collection/ArrayListTest3.java | 2 +- .../src/main/java/com/wdbyte/collection/ArrayListTest4.java | 2 +- .../src/main/java/com/wdbyte/thread/CompletableFutureTest.java | 2 +- .../src/main/java/com/wdbyte/collection/EnumMapTest.java | 2 +- .../src/main/java/com/wdbyte/collection/JavaArrays.java | 2 +- .../src/main/java/com/wdbyte/io/file/FileAppendDemo.java | 2 +- .../main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java | 2 +- .../src/main/java/com/wdbyte/io/file/FileDelete.java | 2 +- .../src/main/java/com/wdbyte/io/file/FileReadDemo.java | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java index b7e6be5..cbe7f6f 100644 --- a/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java +++ b/core-java-modules/core-java-22/src/main/java/com/wdbyte/Main.java @@ -1,7 +1,7 @@ package com.wdbyte; /** - * @author niulang + * @author www.wdbyte.com * @date 2025/04/28 */ //TIP To Run code, press or diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java index 9d808cf..50490a5 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo1.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/04/22 */ public class AssertDemo1 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java index 1cc5664..735c2bb 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo2.java @@ -6,7 +6,7 @@ import static com.google.common.base.Verify.*; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/04/22 */ public class AssertDemo2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java index 3f02ca2..09e2272 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo3.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/04/22 */ public class AssertDemo3 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java index b324a0d..565de43 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo4.java @@ -4,7 +4,7 @@ import java.util.List; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/04/22 */ public class AssertDemo4 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java index 6d008c4..4c37dde 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/assert1/AssertDemo5.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Assertions; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/04/22 */ public class AssertDemo5 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java index 54032e0..582a6f3 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest.java @@ -8,7 +8,7 @@ import java.util.stream.Collectors; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/10/19 */ public class ArrayListTest { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java index 357e0f2..f18c9b1 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest2.java @@ -6,7 +6,7 @@ import java.util.Vector; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/10/19 */ public class ArrayListTest2 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java index 02b189f..a88fd7a 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest3.java @@ -9,7 +9,7 @@ import com.google.common.collect.Lists; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/10/19 */ public class ArrayListTest3 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java index d49f5b2..93d09e4 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/collection/ArrayListTest4.java @@ -12,7 +12,7 @@ import com.google.common.collect.Lists; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/10/19 */ public class ArrayListTest4 { diff --git a/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java index af22eb6..7887015 100644 --- a/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java +++ b/core-java-modules/core-java-base/src/main/java/com/wdbyte/thread/CompletableFutureTest.java @@ -6,7 +6,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/11/01 */ public class CompletableFutureTest { diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java index 4646e7d..1370449 100644 --- a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java +++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/EnumMapTest.java @@ -3,7 +3,7 @@ import java.util.EnumMap; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/10/20 */ public class EnumMapTest { diff --git a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java index 47da76b..144cdcb 100644 --- a/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java +++ b/core-java-modules/core-java-collect/src/main/java/com/wdbyte/collection/JavaArrays.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2024/03/04 */ public class JavaArrays { diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java index 6d43afb..53879c8 100644 --- a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileAppendDemo.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/12/12 */ public class FileAppendDemo { diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java index 4f4dddd..158942d 100644 --- a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileCreateAndWriteDemo.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/11/06 */ public class FileCreateAndWriteDemo { diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java index 5882f01..f865382 100644 --- a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileDelete.java @@ -9,7 +9,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/12/18 */ public class FileDelete { diff --git a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java index 87e35b2..276e0e8 100644 --- a/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java +++ b/core-java-modules/core-java-io/src/main/java/com/wdbyte/io/file/FileReadDemo.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; /** - * @author niulang + * @author www.wdbyte.com * @date 2023/11/08 */ public class FileReadDemo { From b845e908161ae9e3882458bb99b7361ac235753f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E6=9C=97?= Date: Thu, 22 Jan 2026 00:08:52 +0800 Subject: [PATCH 104/105] add spring-mcp-server-manual --- .../.mvn/wrapper/maven-wrapper.properties | 3 + spring-ai/spring-mcp-server-manual/README.md | 26 ++ spring-ai/spring-mcp-server-manual/mvnw | 295 ++++++++++++++++++ spring-ai/spring-mcp-server-manual/mvnw.cmd | 189 +++++++++++ spring-ai/spring-mcp-server-manual/pom.xml | 42 +++ .../ai/mcp/manual/McpWeatherController.java | 119 +++++++ .../SpringMcpServerManualApplication.java | 13 + .../src/main/resources/application.properties | 1 + 8 files changed, 688 insertions(+) create mode 100644 spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties create mode 100644 spring-ai/spring-mcp-server-manual/README.md create mode 100755 spring-ai/spring-mcp-server-manual/mvnw create mode 100644 spring-ai/spring-mcp-server-manual/mvnw.cmd create mode 100644 spring-ai/spring-mcp-server-manual/pom.xml create mode 100644 spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java create mode 100644 spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java create mode 100644 spring-ai/spring-mcp-server-manual/src/main/resources/application.properties diff --git a/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties b/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..8dea6c2 --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.12/apache-maven-3.9.12-bin.zip diff --git a/spring-ai/spring-mcp-server-manual/README.md b/spring-ai/spring-mcp-server-manual/README.md new file mode 100644 index 0000000..af5c1b2 --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/README.md @@ -0,0 +1,26 @@ +这个基于 Spring Boot 和 Fastjson 的 MCP(Model Context Protocol)服务实现,是一个遵循 JSON-RPC 2.0 规范的轻量级工具服务端,旨在为大语言模型(LLM)提供标准化的外部能力接口 36。 + +以下是该实现的核心架构与技术特点介绍: + +### 1. 核心协议架构 +该实现采用了 MCP 的 Streamable HTTP 传输机制,通过单一的 HTTP POST 端点(/mcp)处理所有交互逻辑 26。 + +消息格式:所有请求和响应均严格遵循 JSON-RPC 2.0 结构,包含 jsonrpc、id、method 和 params 字段 6。 +生命周期管理:手动实现了 MCP 协议定义的完整链路,包括初始化握手(initialize)、初始化完成通知(notifications/initialized)、工具发现(tools/list)以及工具执行(tools/call) 56。 +### 2. Java 21 技术优化 +利用 Java 21 的现代语法特性极大简化了协议模版代码: + +Record 记录类:使用 record 定义 JsonRpcRequest、Tool 和 ToolCallResult 等数据模型。这消除了 Getter/Setter 等样板代码,确保了消息对象的不可变性,并自动支持 JSON 序列化。 +Switch 表达式:在控制器中使用增强的 switch 表达式处理 method 分发。这种方式比传统的 if-else 更具读性,且利用 yield 关键字实现了逻辑的紧凑闭环。 +文本块(Text Blocks):利用 """ 语法定义工具的 JSON Schema。这使得复杂的输入参数描述(如 inputSchema)在代码中能以原始 JSON 格式直观呈现,便于维护 5。 +### 3. 工具定义与执行逻辑 +该服务模拟了一个名为 getWeather 的城市天气查询工具: + +工具发现:在 tools/list 阶段,服务端会返回该工具的名称、描述以及基于 JSON Schema 的参数定义(要求必填 city 字符串),以便 LLM 理解如何调用该工具 56。 +参数解析:通过 Fastjson 的 JSONObject 直接处理动态参数。在 tools/call 触发时,程序会从 arguments 映射中提取城市名称,并返回标准化的内容结构。 +响应规范:响应体封装在 content 数组中,并包含 isError 标识,这符合 MCP 对工具执行结果的标准化要求 6。 +### 4. 最佳实践体现 +轻量化:不依赖于复杂的 MCP 官方 SDK,仅通过 Spring Boot 基础框架和 Fastjson 实现,适合快速集成到现有生产微服务中 2。 +无状态处理:服务设计为无状态,符合 MCP Streamable HTTP 的简化模式,便于水平扩展。 +错误处理基础:虽然为简易版,但结构上预留了 isError 字段,允许在工具内部出错时让 LLM 感知并尝试自我修正 6。 +这种实现方式展示了如何通过极简的代码量构建符合开放协议标准的 AI 插件系统,降低了 LLM 与私有数据源及外部工具对接的复杂度 13。 \ No newline at end of file diff --git a/spring-ai/spring-mcp-server-manual/mvnw b/spring-ai/spring-mcp-server-manual/mvnw new file mode 100755 index 0000000..bd8896b --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/mvnw @@ -0,0 +1,295 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Apache Maven Wrapper startup batch script, version 3.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + if [ -n "${JAVA_HOME-}" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/spring-ai/spring-mcp-server-manual/mvnw.cmd b/spring-ai/spring-mcp-server-manual/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Apache Maven Wrapper startup batch script, version 3.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/spring-ai/spring-mcp-server-manual/pom.xml b/spring-ai/spring-mcp-server-manual/pom.xml new file mode 100644 index 0000000..1e9f19e --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 4.0.1 + + + com.wdbyte.ai.mcp.manual + spring-mcp-server-manual + 0.0.1-SNAPSHOT + spring-mcp-server-manual + spring-mcp-server-manual + + 21 + + + + org.springframework.boot + spring-boot-starter-webmvc + + + + com.alibaba.fastjson2 + fastjson2 + 2.0.60 + compile + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java new file mode 100644 index 0000000..76f2cb8 --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/McpWeatherController.java @@ -0,0 +1,119 @@ +package com.wdbyte.ai.mcp.manual; + +import java.util.List; +import java.util.Map; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.alibaba.fastjson2.JSONWriter.Feature; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/mcp") +public class McpWeatherController { + + private static final Logger log = LoggerFactory.getLogger(McpWeatherController.class); + + // 1. 静态化工具定义,使 tools/list 极其简洁 + private static final List AVAILABLE_TOOLS = List.of( + new Tool("getWeather", "获取指定城市的天气预报", + JSONObject.parseObject(""" + { + "type": "object", + "properties": { "city": { "type": "string", "description": "城市名" } }, + "required": ["city"], + "additionalProperties": false + } + """)) + ); + + @PostMapping(consumes = "application/json", produces = "application/json") + public ResponseEntity handleMcpRequest(@RequestBody JsonRpcRequest request) { + Object id = request.id(); + + var response = switch (request.method()) { + case "initialize" -> ok(id, new InitializeResult()); + case "notifications/initialized" -> accepted(); + case "ping" -> ok(id, Map.of()); + case "tools/list" -> ok(id, Map.of("tools", AVAILABLE_TOOLS)); + case "tools/call" -> handleToolCall(id, request.params()); + default -> ResponseEntity.notFound().build(); + }; + log.info("\nrequest: {}\nresponse: {}", JSON.toJSONString(request), JSON.toJSONString(response.getBody())); + return response; + } + + /** + * 优雅处理工具调用:直接通过 JSONObject 转换,无需 String 二次中转 + */ + private ResponseEntity handleToolCall(Object id, JSONObject params) { + if (params == null) return badRequest(); + + var callParams = params.toJavaObject(ToolCallParams.class); + + // 使用 switch 处理多工具扩展性更好 + return switch (callParams.name()) { + case "getWeather" -> { + String city = String.valueOf(callParams.arguments().getOrDefault("city", "未知城市")); + yield ok(id, new ToolCallResult(city + "今日雷暴雨,建议居家")); + } + default -> badRequest(); + }; + } + + // --- 辅助方法 --- + private static ResponseEntity ok(Object id, Object result) { + return ResponseEntity.ok(new JsonRpcResponse(id, result)); + } + + private static ResponseEntity accepted() { + return ResponseEntity.status(202).build(); + } + + private static ResponseEntity badRequest() { + return ResponseEntity.badRequest().build(); + } + + // --- MCP 协议 Records (Java 21) --- + + // 将 params 定义为 JSONObject,方便后续 toJavaObject 转换 + public record JsonRpcRequest(String jsonrpc, Object id, String method, JSONObject params) {} + + public record JsonRpcResponse(String jsonrpc, Object id, Object result) { + public JsonRpcResponse(Object id, Object result) { + this("2.0", id, result); + } + } + + // 初始化结果模型 + public record InitializeResult(String protocolVersion, Capabilities capabilities, ServerInfo serverInfo) { + public InitializeResult() { + this("2025-06-18", new Capabilities(new Tools(false)), new ServerInfo("mcp-weather-server", "1.0.0")); + } + } + + public record ServerInfo(String name, String version) {} + public record Capabilities(Tools tools) {} + public record Tools(boolean listChanged) {} + + // 工具定义模型 + public record Tool(String name, String description, Object inputSchema) {} + + // 工具调用参数模型 + public record ToolCallParams(String name, Map arguments) {} + + // 响应内容模型 + public record Content(String type, String text) { + public Content(String text) { this("text", text); } + } + + public record ToolCallResult(List content, boolean isError) { + public ToolCallResult(String text) { + this(List.of(new Content(text)), false); + } + } +} diff --git a/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java new file mode 100644 index 0000000..48f4ffe --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/src/main/java/com/wdbyte/ai/mcp/manual/SpringMcpServerManualApplication.java @@ -0,0 +1,13 @@ +package com.wdbyte.ai.mcp.manual; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class SpringMcpServerManualApplication { + + public static void main(String[] args) { + SpringApplication.run(SpringMcpServerManualApplication.class, args); + } + +} diff --git a/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties b/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties new file mode 100644 index 0000000..bbc9400 --- /dev/null +++ b/spring-ai/spring-mcp-server-manual/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=spring-mcp-server-manual From 5eb085a1211442df4baf458fde27ae9ed7a06755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=89=9B=E6=9C=97?= Date: Thu, 22 Jan 2026 00:28:33 +0800 Subject: [PATCH 105/105] update readme --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 763ca75..3d12432 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ > Hi there 👋 我是阿朗, 一名 Java 开发者,热衷于分享一些通俗易懂的技术文章。 分享几句鸡汤,长寿在于生活规律;成功在于坚持不懈。 做好的事情,而不是好做的事情。 +## AI 开发 + +- [MCP Streamable HTTP 协议入门与 100 行代码实现](https://wdbyte.com/spring-mcp-server-manual/) + ## ⏳ Java 开发 - [如何破解滑动验证码?](https://www.wdbyte.com/java/img-verification/)