diff --git a/.gitignore b/.gitignore index 9c63d81..3540bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,10 @@ Spring-Boot/learnsb.iml Spring/learnspring.iml Spring-AOP/learnaop.iml Spring-Netty/Spring-Netty.iml +Spring-Netty/learnnetty.iml Spring-Security/SpringSecurityDemo.iml Spring-Security/springsecurity.iml +rocketmqdemo/rocketmqdemo.iml # target JdkLearn/target @@ -17,6 +19,7 @@ Spring-AOP/target Spring-Boot/target Spring-Netty/target Spring-Security/target +rocketmqdemo/target # .DO_Store .DS_Store diff --git a/JdkLearn/pom.xml b/JdkLearn/pom.xml index d196fc9..9fb74db 100644 --- a/JdkLearn/pom.xml +++ b/JdkLearn/pom.xml @@ -20,6 +20,18 @@ + + + com.github.ben-manes.caffeine + caffeine + + + + + io.netty + netty-all + + org.springframework.boot diff --git a/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java b/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java new file mode 100644 index 0000000..aca54da --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/collection/LRUCache.java @@ -0,0 +1,71 @@ +package com.learnjava.collection; + +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Spliterator; +import java.util.function.Consumer; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/6 + */ +public class LRUCache implements Iterable { + + private int MAX = 3; + private LinkedHashMap cache = new LinkedHashMap<>(); + + public void cache(K key, V value) { + if (cache.containsKey(key)) { + cache.remove(key); + } else if (cache.size() >= MAX) { + Iterator iterator = cache.keySet().iterator(); + K first = iterator.next(); + cache.remove(first); + } + cache.put(key, value); + } + + public V getValue(K k) { + return cache.get(k); + } + + @Override + public void forEach(Consumer action) { + Iterable.super.forEach(action); + } + + @Override + public Spliterator spliterator() { + return Iterable.super.spliterator(); + } + + @Override + public Iterator iterator() { + Iterator iterator = cache.keySet().iterator(); + return new Iterator() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public K next() { + return iterator.next(); + } + }; + } + + public static void main(String[] args) { + LRUCache cache = new LRUCache<>(); + cache.cache("1", "1A"); + cache.cache("2", "2A"); + cache.cache("3", "3A"); + cache.cache("1", "1A"); + + for (String next : cache) { + System.out.println(cache.getValue(next)); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java new file mode 100644 index 0000000..0d04fb8 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/ReentrantLockDemo.java @@ -0,0 +1,9 @@ +package com.learnjava.concurent; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/7 + */ +public class ReentrantLockDemo { +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdDemo.java new file mode 100644 index 0000000..aa00a30 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdDemo.java @@ -0,0 +1,12 @@ +package com.learnjava.concurent; + +/** + * @author lhy + * @date 2021/7/27 + */ +public class SnowIdDemo { + public static void main(String[] args) { + // 通过雪花秀算法获取分布式id + System.out.println(SnowIdUtils.uniqueLongHex()); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java new file mode 100644 index 0000000..384d980 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/SnowIdUtils.java @@ -0,0 +1,114 @@ +package com.learnjava.concurent; + +/** + * @author lhy + * @date 2021/7/27 + */ +public class SnowIdUtils { + /** + * 私有的 静态内部类 + */ + private static class SnowFlake { + + /** + * 内部类对象(单例模式) + */ + private static final SnowIdUtils.SnowFlake SNOW_FLAKE = new SnowIdUtils.SnowFlake(); + /** + * 起始的时间戳 + */ + private final long START_TIMESTAMP = 1609464335121L; + /** + * 序列号占用位数 + */ + private final long SEQUENCE_BIT = 12; + /** + * 机器标识占用位数 + */ + private final long MACHINE_BIT = 10; + /** + * 时间戳位移位数 + */ + private final long TIMESTAMP_LEFT = SEQUENCE_BIT + MACHINE_BIT; + /** + * 最大序列号 (4095) + */ + private final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT); + /** + * 最大机器编号 (1023) + */ + private final long MAX_MACHINE_ID = ~(-1L << MACHINE_BIT); + /** + * 生成id机器标识部分 + */ + private long machineIdPart; + /** + * 序列号 + */ + private long sequence = 0L; + /** + * 上一次时间戳 + */ + private long lastStamp = -1L; + + /** + * 构造函数初始化机器编码 + */ + private SnowFlake() { +// String ip = instance.getDockerIp().replace(".", ""); + // 模拟获取机器节点ip + String ip = "127.0.0.1"; + long localIp = Long.parseLong(ip.replace(".", "")); + machineIdPart = (localIp & MAX_MACHINE_ID) << SEQUENCE_BIT; + } + /** + * 获取雪花ID + */ + public synchronized long nextId() { + long currentStamp = timeGen(); + while (currentStamp < lastStamp) { + throw new RuntimeException(String.format("时钟已经回拨. Refusing to generate id for %d milliseconds", lastStamp - currentStamp)); + } + if (currentStamp == lastStamp) { + sequence = (sequence + 1) & MAX_SEQUENCE; + if (sequence == 0) { + currentStamp = getNextMill(); + } + } else { + sequence = 0L; + } + lastStamp = currentStamp; + return (currentStamp - START_TIMESTAMP) << TIMESTAMP_LEFT | machineIdPart | sequence; + } + /** + * 阻塞到下一个毫秒,直到获得新的时间戳 + */ + private long getNextMill() { + long mill = timeGen(); + // + while (mill <= lastStamp) { + mill = timeGen(); + } + return mill; + } + /** + * 返回以毫秒为单位的当前时间 + */ + protected long timeGen() { + return System.currentTimeMillis(); + } + } + + /** + * 获取long类型雪花ID + */ + public static long uniqueLong() { + return SnowIdUtils.SnowFlake.SNOW_FLAKE.nextId(); + } + /** + * 获取String类型雪花ID + */ + public static String uniqueLongHex() { + return String.format("%016X", uniqueLong()); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java new file mode 100644 index 0000000..4a89467 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/SynchronizeDemo.java @@ -0,0 +1,9 @@ +package com.learnjava.concurent; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/7 + */ +public class SynchronizeDemo { +} diff --git a/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java b/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java new file mode 100644 index 0000000..5d5b5fc --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java @@ -0,0 +1,48 @@ +package com.learnjava.concurent; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/4/24 + */ +public class ThreadPoolExecutorDemo { + + public static void main(String[] args) { + testThreadPoolExecutorBinaryCalc(); + } + + + /** + * 验证ThreadPoolExecutor中的二进制位运算操作 + */ + private static void testThreadPoolExecutorBinaryCalc() { +// System.out.println(ctl.get()); +// System.out.println(Integer.toBinaryString(ctlOf(RUNNING, 0))); +// System.out.println(Integer.toBinaryString(RUNNING)); + // 修改线程状态-STOP + System.out.println(Integer.toBinaryString(~runStateOf(ctlOf(STOP, 10)))); + // 修改线程状态-TERMINATED +// System.out.println(runStateOf(3)); +// System.out.println(Integer.toBinaryString(~CAPACITY)); + } + + private static final int COUNT_BITS = Integer.SIZE - 3; + + private static final int CAPACITY = (1 << COUNT_BITS) - 1; + + private static final int RUNNING = -1 << COUNT_BITS; + private static final int SHUTDOWN = 0 << COUNT_BITS; + private static final int STOP = 1 << COUNT_BITS; + private static final int TIDYING = 2 << COUNT_BITS; + private static final int TERMINATED = 3 << COUNT_BITS; + + private static AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); + + private static int runStateOf(int c) { return c & ~CAPACITY; } + + private static int workerCountOf(int c) { return c & CAPACITY; } + + private static int ctlOf(int rs, int wc) { return rs | wc; } +} diff --git a/JdkLearn/src/main/java/com/learnjava/io/bio/BIO.java b/JdkLearn/src/main/java/com/learnjava/io/bio/BIO.java new file mode 100644 index 0000000..4226be5 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/io/bio/BIO.java @@ -0,0 +1,47 @@ +package com.learnjava.io.bio; + +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * @author lhy + * + * 在windows服务器下,可以使用telnet来合serversocket建立连接 + */ +public class BIO { + public static void main(String[] args) throws Exception { + ServerSocket serverSocket = new ServerSocket(666); + System.out.println("Server started..."); + while (true) { + System.out.println("socket accepting..."); + Socket socket = serverSocket.accept(); + new Thread(new Runnable() { + @Override + public void run() { + try { + byte[] bytes = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + while (true) { + System.out.println("reading..."); + int read = inputStream.read(bytes); + if (read != -1) { + System.out.println(new String(bytes, 0, read)); + } else { + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }).start(); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/nettysource/Client.java b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/Client.java similarity index 97% rename from JdkLearn/src/main/java/com/learnjava/nettysource/Client.java rename to JdkLearn/src/main/java/com/learnjava/io/netty/bio/Client.java index 5514bee..f84b58e 100644 --- a/JdkLearn/src/main/java/com/learnjava/nettysource/Client.java +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/Client.java @@ -1,4 +1,4 @@ -package com.learnjava.nettysource; +package com.learnjava.io.netty.bio; import java.io.IOException; import java.net.Socket; diff --git a/JdkLearn/src/main/java/com/learnjava/nettysource/ClientHandler.java b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/ClientHandler.java similarity index 96% rename from JdkLearn/src/main/java/com/learnjava/nettysource/ClientHandler.java rename to JdkLearn/src/main/java/com/learnjava/io/netty/bio/ClientHandler.java index d0ddba9..d2d6b88 100644 --- a/JdkLearn/src/main/java/com/learnjava/nettysource/ClientHandler.java +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/ClientHandler.java @@ -1,4 +1,4 @@ -package com.learnjava.nettysource; +package com.learnjava.io.netty.bio; import java.io.IOException; import java.io.InputStream; diff --git a/JdkLearn/src/main/java/com/learnjava/nettysource/Server.java b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/Server.java similarity index 94% rename from JdkLearn/src/main/java/com/learnjava/nettysource/Server.java rename to JdkLearn/src/main/java/com/learnjava/io/netty/bio/Server.java index 834f41b..af55558 100644 --- a/JdkLearn/src/main/java/com/learnjava/nettysource/Server.java +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/Server.java @@ -1,4 +1,4 @@ -package com.learnjava.nettysource; +package com.learnjava.io.netty.bio; import java.io.IOException; import java.net.ServerSocket; @@ -30,7 +30,7 @@ public void run() { } public void doStart() { - while (true) { + for (;;) { try { Socket client = serverSocket.accept(); new ClientHandler(client).start(); diff --git a/JdkLearn/src/main/java/com/learnjava/nettysource/ServerBoot.java b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/ServerBoot.java similarity index 76% rename from JdkLearn/src/main/java/com/learnjava/nettysource/ServerBoot.java rename to JdkLearn/src/main/java/com/learnjava/io/netty/bio/ServerBoot.java index 9348e09..bbb2762 100644 --- a/JdkLearn/src/main/java/com/learnjava/nettysource/ServerBoot.java +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/bio/ServerBoot.java @@ -1,4 +1,4 @@ -package com.learnjava.nettysource; +package com.learnjava.io.netty.bio; /** @@ -9,6 +9,6 @@ public class ServerBoot { public static void main(String[] args) { Server server = new Server(PORT); - + server.start(); } } diff --git a/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/Server.java b/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/Server.java new file mode 100644 index 0000000..7c1d994 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/Server.java @@ -0,0 +1,53 @@ +package com.learnjava.io.netty.demo01; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.AttributeKey; + +/** + * @author lhy + * @date 2021/5/21 + */ +public class Server { + + public static void main(String[] args) throws Exception { + + /** + * EventLoopGroup: + * NioEventLoopGruop + * NioEventLoop + */ + + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.TCP_NODELAY, true) + .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") + .handler(new ServerHandler()) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { +// ch.pipeline().addLast(new AuthHandler()); + //.. + + } + }); + + ChannelFuture future = bootstrap.bind(8888).sync(); + + future.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/ServerHandler.java b/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/ServerHandler.java new file mode 100644 index 0000000..7123116 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/io/netty/demo01/ServerHandler.java @@ -0,0 +1,74 @@ +package com.learnjava.io.netty.demo01; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2021/5/22 + */ +public class ServerHandler extends ChannelInboundHandlerAdapter { + public ServerHandler() { + super(); + } + + @Override + public void channelRegistered(ChannelHandlerContext ctx) throws Exception { + super.channelRegistered(ctx); + } + + @Override + public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { + super.channelUnregistered(ctx); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) throws Exception { + super.channelActive(ctx); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + super.channelInactive(ctx); + } + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + super.channelRead(ctx, msg); + } + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { + super.channelReadComplete(ctx); + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + super.userEventTriggered(ctx, evt); + } + + @Override + public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception { + super.channelWritabilityChanged(ctx); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + super.exceptionCaught(ctx, cause); + } + + @Override + public boolean isSharable() { + return super.isSharable(); + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + super.handlerAdded(ctx); + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + super.handlerRemoved(ctx); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/io/nio/MappedByteBufferTest.java b/JdkLearn/src/main/java/com/learnjava/io/nio/MappedByteBufferTest.java index 4097936..75cf208 100644 --- a/JdkLearn/src/main/java/com/learnjava/io/nio/MappedByteBufferTest.java +++ b/JdkLearn/src/main/java/com/learnjava/io/nio/MappedByteBufferTest.java @@ -1,5 +1,8 @@ package com.learnjava.io.nio; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.RandomAccessFile; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; @@ -17,13 +20,55 @@ public static void main(String[] args) throws Exception { MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5); - mappedByteBuffer.put(0, (byte)'H'); - mappedByteBuffer.put(3, (byte)'9'); + mappedByteBuffer.put(0, (byte) 'H'); + mappedByteBuffer.put(3, (byte) '9'); // IndexOutOfBoundsException - mappedByteBuffer.put(5, (byte)'Y'); + mappedByteBuffer.put(5, (byte) 'Y'); randomAccessFile.close(); System.out.println("change success"); } + + /** + * + * @param from + * @param to + * @throws IOException + */ + public static void mmap4zeroCopy(String from, String to) throws IOException { + FileChannel source = null; + FileChannel destination = null; + try { + source = new RandomAccessFile(from, "r").getChannel(); + destination = new RandomAccessFile(to, "rw").getChannel(); + MappedByteBuffer inMappedBuf = + source.map(FileChannel.MapMode.READ_ONLY, 0, source.size()); + destination.write(inMappedBuf); + } finally { + if (source != null) { + source.close(); + } + if (destination != null) { + destination.close(); + } + } + } + + public static void sendfile4zeroCopy(String from, String to) throws IOException{ + FileChannel source = null; + FileChannel destination = null; + try { + source = new FileInputStream(from).getChannel(); + destination = new FileOutputStream(to).getChannel(); + source.transferTo(0, source.size(), destination); + } finally { + if (source != null) { + source.close(); + } + if (destination != null) { + destination.close(); + } + } + } } diff --git a/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioClinet.java b/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioClinet.java new file mode 100644 index 0000000..aab903b --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioClinet.java @@ -0,0 +1,40 @@ +package com.learnjava.io.nio.demo01; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SocketChannel; +import java.util.Date; +import java.util.Scanner; + +/** + * @author lhy + * @date 2021/5/22 + */ +public class NioClinet { + + public static void main(String[] args) throws Exception { + + // 获取通道 + SocketChannel channel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9999)); + // 切换至非阻塞模式 + channel.configureBlocking(false); + // 分配缓冲区大小 + ByteBuffer buffer = ByteBuffer.allocate(1024); + + Scanner scan = new Scanner(System.in); + + while (scan.hasNext()) { + String next = scan.next(); + // 向缓冲区里写入数据 + buffer.put( (new Date().toString() + "\n" + next).getBytes()); + buffer.flip(); + + // 向通道里写入带有数据的缓冲区对象, 表示向服务器发送数据 + channel.write( buffer); + buffer.clear(); + } + + // 关闭通道 + channel.close(); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioServer.java b/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioServer.java new file mode 100644 index 0000000..7ea41ec --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/io/nio/demo01/NioServer.java @@ -0,0 +1,57 @@ +package com.learnjava.io.nio.demo01; + +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.Iterator; + +/** + * @author lhy + * @date 2021/5/22 + */ +public class NioServer { + public static void main(String[] args) throws Exception { + // 获取服务端通道 + ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); + // 切换为非阻塞模式 + serverSocketChannel.configureBlocking(false); + // 绑定链接 + Selector selector = Selector.open(); + // 将通道注册在selector上,并绑定为读事件 + serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); + // 选择器轮训,阻塞 + while (selector.select() > 0) { + Iterator it = selector.selectedKeys().iterator(); + + // 判断是否有事件进来 + while (it.hasNext()) { + // 获取就绪的事件 + SelectionKey selectionKey = it.next(); + + // 读事件 + if (selectionKey.isAcceptable()) { + // 就绪的客户端连接事件 + SocketChannel acceptChannel = serverSocketChannel.accept(); + acceptChannel.configureBlocking(false); + acceptChannel.register(selector, SelectionKey.OP_READ); + } else if (selectionKey.isReadable()) { + // 读就绪事件 + SocketChannel readAcceptChannel = serverSocketChannel.accept(); + ByteBuffer allocateBuffer = ByteBuffer.allocate(1024); + + int len = 0; + while ((len = readAcceptChannel.read(allocateBuffer)) > 0) { + allocateBuffer.flip(); + System.out.println(new String(allocateBuffer.array(), 0, len)); + allocateBuffer.clear(); + } + } + } + + // 取消选择键selectionKey + it.remove(); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaComparatorDemo.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaComparatorDemo.java new file mode 100644 index 0000000..1db2858 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaComparatorDemo.java @@ -0,0 +1,165 @@ +package com.learnjava.lambda; + +import lombok.AllArgsConstructor; +import lombok.Data; +import sun.java2d.pipe.SpanShapeRenderer; + +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class LambdaComparatorDemo { + + public static String[] arrays = {"Milan", "london", "San Francisco", "Tokyo", "New Delhi"}; + + public static List employees; + + static { + Employee e1 = new Employee(1,23,"M","Rick","Beethovan", "2021-04-01"); + Employee e2 = new Employee(2,13,"F","Martina","Hengis", "2021-04-02"); + Employee e3 = new Employee(3,43,"M","Ricky","Martin","2021-04-09" ); + Employee e4 = new Employee(4,26,"M","Jon","Lowman", "2021-04-10"); + Employee e5 = new Employee(5,19,"F","Cristine","Maria", "2021-04-01"); + Employee e6 = new Employee(6,15,"M","David","Feezor", "2021-04-06"); + Employee e7 = new Employee(7,68,"F","Melissa","Roy", "2021-04-06"); + Employee e8 = new Employee(8,79,"M","Alex","Gussin", "2021-04-08"); + Employee e9 = new Employee(9,15,"F","Neetu","Singh", "2021-04-09"); + Employee e10 = new Employee(10,45,"M","Naveen","Jain", "2021-04-10"); + employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); + } + + public static void main(String[] args) { +// test01(); +// test02(); + test03(); + } + + /** + * List字符串排序 + */ + public static void test01() { + List cities = Arrays.asList(arrays); + + System.out.println(cities); + + // CASE_INSENSITIVE_ORDER 是一个排序器Comparator接口,意思是不区分大小写进行排序 + cities.sort(String.CASE_INSENSITIVE_ORDER); + System.out.println(cities); + + // 自然排序 + cities.sort(Comparator.naturalOrder()); + System.out.println(cities); + + // 可以将排序器放在Stream管道流中 + Stream.of(arrays) + .sorted(Comparator.naturalOrder()) + .forEach(System.out::println); + } + + /** + * 对整数数组进行排序 + */ + public static void test02() { + List numbers = Arrays.asList(6, 2, 4, 3, 1, 9); + System.out.println(numbers); + + // 自然排序(升序) + numbers.sort(Comparator.naturalOrder()); + System.out.println(numbers); + + // 降序 + numbers.sort(Comparator.reverseOrder()); + System.out.println(numbers); + } + + /** + * 对对象进行排序 + */ + public static void test03() { + // 根据employee的年龄进行自然排序 + employees.sort(Comparator.comparing(Employee::getAge)); + employees.forEach(System.out::println); + + System.out.println(); + + // 根据employee的年龄进行降序排序 + employees.sort(Comparator.comparing(Employee::getAge).reversed()); + employees.forEach(System.out::println); + + System.out.println(); + + // 先对性别排序,然后对年龄进行排序 + employees.sort( + Comparator.comparing(Employee::getGender) + .thenComparing(Employee::getAge) + // 性别,年龄都进行倒序排序 + .reversed() + ); + employees.forEach(System.out::println); + + // 自定义排序器 + employees.sort((em1, em2) -> { + if (em1.getAge().equals(em2.getAge())) { + return 0; + } + return em1.getAge() - em2.getAge() > 0 ? -1 : 1; + }); + employees.forEach(System.out::println); + } + + + + + + + + + + + + + + + + + + + + + + + @Data + @AllArgsConstructor + public static class Employee { + + private Integer id; + // 年龄 + private Integer age; + // 性别 + private String gender; + private String firstName; + private String lastName; + private String date; + + @Override + public String toString() { + return "Employee{" + + "id=" + id + + ", age=" + age + + ", gender='" + gender + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", date='" + date + '\'' + + '}'; + } + + // 年龄大于70的谓语 + static Predicate ageGreaterThan70 = e -> e.getAge() > 70; + + static Predicate ageLessThan18 = e -> e.getAge() < 18; + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemo01.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemo01.java new file mode 100644 index 0000000..ae7e1d4 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemo01.java @@ -0,0 +1,37 @@ +package com.learnjava.lambda; + +/** + * @author bruis + * lambda表达式 + */ +public class LambdaDemo01 { + + /** + * 打印内部类 + */ + interface Printer { + void print(String content); +// void print(String content, String operator); + } + + public static void printSomething(String content, Printer printer) { + printer.print(content); + } + + public static void main(String[] args) { +// Printer printer = (String content) -> { +// System.out.println(content); +// }; + +// 去掉参数类型,只有一个参数时可以去掉括号 +// Printer printer = (content) -> { +// System.out.println(content); +// }; + +// 只有一个参数提 +// Printer printer = val -> System.out.println(val); + + Printer printer = System.out::println; + printSomething("hello lambda", printer); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemoForList.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemoForList.java new file mode 100644 index 0000000..e1aa8e6 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaDemoForList.java @@ -0,0 +1,88 @@ +package com.learnjava.lambda; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author bruis + * Lambda的List demo + */ +public class LambdaDemoForList { + + public static String[] arrays = {"Monkey", "Lion", "Giraffe", "Lemur"}; + + public static String[] arrays2 = {"Monkey", "Lion", "Giraffe", "Lemur", "Lion"}; + + public static void main(String[] args) { +// test01(); +// test02(); + test03(); + } + + /** + * 去重Distinct + 排序Sort + */ + public static void test03() { + // 去重 + List uniqueAnimals = Stream.of(arrays2) + .distinct() + .collect(Collectors.toList()); + System.out.println(uniqueAnimals); + // 排序 + List sortedAnimals = Stream.of(arrays) + // 对字母是按照自然顺序进行排序的 + .sorted() + .collect(Collectors.toList()); + System.out.println(sortedAnimals); + } + + /** + * Limit + Skip 数据截取 + */ + public static void test02() { + + /** + * 截取前两位字符串 + */ + List limitN = Stream.of(arrays) + .limit(2) + .collect(Collectors.toList()); + + /** + * 过滤掉前两位元素 + */ + List skipN = Stream.of(arrays) + .skip(2) + .collect(Collectors.toList()); + + System.out.println(limitN); + System.out.println(skipN); + } + + /** + * 过滤 + map处理 + */ + public static void test01() { + List nameStrList = Arrays.asList("abc", "efg", "hig", "hii", "klm"); + + List result = nameStrList + .stream() + .filter(s -> s.startsWith("h")) + .map(String::toUpperCase) + // 调用自定义的方法 + .map(MyStringUtils::myToUpperCase) + .collect(Collectors.toList()); + + for (String name : result) { + System.out.println(name); + } + } + + private static class MyStringUtils { + public static String myToUpperCase(String str) { + return str.toUpperCase(); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapDemo.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapDemo.java new file mode 100644 index 0000000..cc03752 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapDemo.java @@ -0,0 +1,139 @@ +package com.learnjava.lambda; + +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * @author bruis + * 操作Map + */ +public class LambdaMapDemo { + + public static SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); + + public static void main(String[] args) { +// test01(); +// test02(); + test03(); +// test04(); + } + + /** + * 需求,将employee集合中的元素根据date进行排序 + */ + public static void test01() { + List sortedByDate = LambdaComparatorDemo.employees + .stream() + .sorted() + .sorted((a, b) -> { + int result; + try { + result = getDate(b.getDate()).compareTo(getDate(a.getDate())); + } catch (Exception e) { + result = 0; + } + return result; + }) + .collect(Collectors.toList()); + System.out.println(sortedByDate); + } + + /** + * HashMap的merge方法,如果key相同,则通过merge来对key相同的元素进行处理 + */ + public static void test02() { + String key = "money"; + Map map = new HashMap(){{put(key, 100);}}; + + // 第三个参数时BiFunction,聚合函数(可以这么理解) + map.merge(key,100,(oldValue, newValue) -> oldValue + newValue); +// map.merge(key, 100,Integer::sum); + System.out.println(map); + } + + /** + * 对map进行排序 + */ + public static void test03() { + Map codes = new HashMap<>(); + codes.put("2021-03", 1); + codes.put("2021-02", 49); + codes.put("2021-05", 33); +// codes.put("2021-04-01", 1); +// codes.put("2021-04-15", 49); +// codes.put("2021-04-10", 33); +// codes.put("2021-04-05", 86); +// codes.put("2021-04-20", 92); + + // 先将Map转化为List,通过collect处理后再转为Map + Map sortedMap = codes.entrySet() + .stream() +// .sorted((c1, c2) -> c2.getValue().compareTo(c1.getValue())) +// .sorted(Map.Entry.comparingByValue()) + .sorted((c1, c2) -> c2.getKey().compareTo(c1.getKey())) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + (oldVal, newVal) -> oldVal, + LinkedHashMap::new + ) + ); + sortedMap.entrySet().forEach(System.out::println); + + } + + /** + * 将list转为map,并用其中一个value作为key + */ + public static void test04() { + LinkedHashMap collect = LambdaComparatorDemo.employees + .stream() + .collect( + Collectors.toMap( + LambdaComparatorDemo.Employee::getDate, + // 这样是返回本身对象的一个表达式,还可以用Function.identity() + // Function.indentity() 就是 t -> t +// employee -> employee, + Function.identity(), + (oldVal, newVal) -> { + // 重复的key就将年纪相加,然后FirstName通过--->加起来 + oldVal.setAge(oldVal.getAge() + newVal.getAge()); + oldVal.setFirstName(oldVal + .getFirstName() + .concat("--->") + .concat(newVal.getFirstName())); + return oldVal; + }, + LinkedHashMap::new + ) + ); + // 这样打印出的map元素不好观察 +// System.out.println(collect); +// collect.entrySet().forEach(System.out::println); + + LinkedHashMap sortedCollect = collect + .entrySet() + .stream() + .sorted((a, b) -> b.getKey().compareTo(a.getKey())) + .collect( + Collectors.toMap( + Map.Entry::getKey, + Map.Entry::getValue, + // 上面已经对重复key做处理了,这里就直接范围oldVal就行 + (oldVal, newVal) -> oldVal, + LinkedHashMap::new + ) + ); + + // 根据日期排序 + sortedCollect.entrySet() + .forEach(System.out::println); + } + + public static Date getDate(String date) throws Exception { + return format.parse(date); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapMerge.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapMerge.java new file mode 100644 index 0000000..d78e73f --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaMapMerge.java @@ -0,0 +1,58 @@ +package com.learnjava.lambda; + +import java.util.HashMap; +import java.util.Map; + +/** + * 关于Map的合并操作 + * + * @author lhy + * @date 2021/7/20 + */ +public class LambdaMapMerge { + + public static void main(String[] args) { +// mapMerge(); +// mapMerge2(); + } + + /** + * value为int类型的map merge操作,将两个map,相同key merge在一起 + * + * key:string + * value:int + */ + public static void mapMerge() { + Map map1= new HashMap<>(); + map1.put("one",1); + map1.put("two",2); + map1.put("three",3); + Map map2= new HashMap<>(); + map2.put("one",1); + map2.put("two",2); + + map1.forEach((key, value) -> map2.merge(key, value, Integer::sum)); + System.out.println(map2); + } + + /** + * value为int类型的map merge操作,将两个map,相同key merge在一起 + * + * key:string + * value:String + */ + public static void mapMerge2() { + Map map1= new HashMap<>(); + map1.put("one","1"); + map1.put("two","2"); + map1.put("three","3"); + Map map2= new HashMap<>(); + map2.put("one","1"); + map2.put("two","2"); + + map1.forEach((key, value) -> map2.merge(key, value,(total, num) -> String.valueOf(Integer.parseInt(total) + Integer.parseInt(num)))); + + System.out.println(map2); + + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/LambdaReduceDemo.java b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaReduceDemo.java new file mode 100644 index 0000000..b656747 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/LambdaReduceDemo.java @@ -0,0 +1,104 @@ +package com.learnjava.lambda; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; + +/** + * @author bruis + * 累加器 + */ +public class LambdaReduceDemo { + + public static void main(String[] args) { +// test01(); +// test02(); + test03(); + } + + /** + * 对整形进行操作 + */ + public static void test01() { + List numbers = Arrays.asList(1, 2, 3, 4, 5); + Integer reduce = numbers + .stream() + .reduce(0, LambdaReduceDemo::mySum); +// .reduce(0, Integer::sum); +// .reduce(0, (total, element) -> total + element); + System.out.println(reduce); + } + + /** + * 对字符串进行操作 + */ + public static void test02() { + List letters = Arrays.asList("a", "b", "c", "d", "e"); + String reduce = letters + .stream() + .reduce("", String::concat); +// .reduce("", (totol, element) -> totol.concat(element)); + System.out.println(reduce); + } + + /** + * 操作Employee集合元素,将所有员工的年龄通过reduce累加起来 + * + * U identity, BiFunction accumulator, BinaryOperator combiner + * reduce的三个参数: + * 1)初始值; + * 2)累加器(可自己实现逻辑) + * 3) 合并器(parallelStream模式时的合并) + * + */ + public static void test03() { + // 将Employee集合元素转化为Integer集合元素(流) + // map的操作就是将a类型元素转化为b类型元素 + Stream integerStream = LambdaComparatorDemo + .employees + .stream() + .map(LambdaComparatorDemo.Employee::getAge); + + // 求所有员工的年龄 + Integer totalAge = integerStream.reduce(0, Integer::sum); + System.out.println(totalAge); + + // 数据量大的话,可以用并行计算 + // 先讲员工集合转化为“并行流” + Stream parallelStream = LambdaComparatorDemo + .employees + .parallelStream() + .map(LambdaComparatorDemo.Employee::getAge); + + // 相比于普通的单个流,parallelStream多了个合并器,将多个CPU计算的结果再合并到同一个流 + Integer reduce = parallelStream.reduce(0, Integer::sum, Integer::sum); + System.out.println(reduce); + + // 可以不用map将employee转化为Integer对象,可以直接reduce操作,最后通过Integer::sum这个合并器来将结果合并为Integer类型 + Integer total = LambdaComparatorDemo + .employees + .stream() + .reduce(0, (subTotal, emp) -> subTotal + emp.getAge(), Integer::sum); + System.out.println(total); + + Integer total2 = LambdaComparatorDemo + .employees + .stream() + .reduce(0, LambdaReduceDemo::mySum2, Integer::sum); + System.out.println(total2); + } + + /** + * 可作为BiFunction,传入到reduce作为入参 + * @param a + * @param b + * @return + */ + public static Integer mySum(int a, int b) { + return a + b; + } + + public static Integer mySum2(int a, LambdaComparatorDemo.Employee employee) { + return a + employee.getAge(); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/StreamMatchDemo.java b/JdkLearn/src/main/java/com/learnjava/lambda/StreamMatchDemo.java new file mode 100644 index 0000000..3962b48 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/StreamMatchDemo.java @@ -0,0 +1,49 @@ +package com.learnjava.lambda; + +import java.util.Optional; + +/** + * @author bruis + */ +public class StreamMatchDemo { + + public static void main(String[] args) { +// test01(); + test02(); + } + + /** + * 判断是否有年龄大于70的员工 + */ + public static void test01() { + boolean isExistAgeThan70 = LambdaComparatorDemo.employees + .stream() + // 使用了Employee的谓语语句(这种写法方便复用) + .anyMatch(LambdaComparatorDemo.Employee.ageGreaterThan70); +// .anyMatch(e -> e.getAge() > 70); + System.out.println(isExistAgeThan70); + + boolean isExistAgeLessThan18 = LambdaComparatorDemo.employees + .stream() + .noneMatch(LambdaComparatorDemo.Employee.ageLessThan18); + + System.out.println(isExistAgeLessThan18); + } + + /** + * 元素查找与Optional + */ + public static void test02() { + + Optional employeeOptional = LambdaComparatorDemo.employees + .stream() + .filter(e -> e.getAge() > 400) + .findFirst(); + + // Optional#get 会报空 +// System.out.println(employeeOptional.get()); + LambdaComparatorDemo.Employee employee = employeeOptional.orElse(null); + System.out.println(employee == null); + } + +} diff --git a/JdkLearn/src/main/java/com/learnjava/lambda/StreamParallelDemo.java b/JdkLearn/src/main/java/com/learnjava/lambda/StreamParallelDemo.java new file mode 100644 index 0000000..93c120c --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/lambda/StreamParallelDemo.java @@ -0,0 +1,10 @@ +package com.learnjava.lambda; + +/** + * + * 串行、并行 todo + * + * @author bruis + */ +public class StreamParallelDemo { +} diff --git a/JdkLearn/src/main/java/com/learnjava/optimization/BeanCopierDemo.java b/JdkLearn/src/main/java/com/learnjava/optimization/BeanCopierDemo.java new file mode 100644 index 0000000..5068206 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/optimization/BeanCopierDemo.java @@ -0,0 +1,170 @@ +package com.learnjava.optimization; + +import org.springframework.cglib.beans.BeanCopier; + +/** + * + * 经过测试,BeanCopier性能是BeanUtils10倍左右。 + * + * BeanCopier拷贝速度快,性能瓶颈出现在创建BeanCopier实例的过程中。 所以,把创建过的BeanCopier实例放到缓存中,下次可以直接获取,提升性能: + * + * + * @author lhy + * @date 2021/7/21 + */ +public class BeanCopierDemo { + + private static final BeanCopier BEAN_COPIER = BeanCopier.create(Person.class, PersonVo.class, false); + + public static void main(String[] args) { + Person person = new Person("zs", "high School", 16, 177, 126); + PersonVo vo = new PersonVo(); + + BEAN_COPIER.copy(person, vo, null); + + System.out.println(vo); + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + public static class PersonVo { + private String name; + private String grade; + private Integer age; + private Integer height; + private Integer weight; + + @Override + public String toString() { + return "PersonVo{" + + "name='" + name + '\'' + + ", grade='" + grade + '\'' + + ", age=" + age + + ", height=" + height + + ", weight=" + weight + + '}'; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGrade() { + return grade; + } + + public void setGrade(String grade) { + this.grade = grade; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Integer getWeight() { + return weight; + } + + public void setWeight(Integer weight) { + this.weight = weight; + } + } + + public static class Person { + private String name; + private String grade; + private Integer age; + private Integer height; + private Integer weight; + + public Person(String name, String grade, Integer age, Integer height, Integer weight) { + this.name = name; + this.grade = grade; + this.age = age; + this.height = height; + this.weight = weight; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getGrade() { + return grade; + } + + public void setGrade(String grade) { + this.grade = grade; + } + + public Integer getAge() { + return age; + } + + public void setAge(Integer age) { + this.age = age; + } + + public Integer getHeight() { + return height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + public Integer getWeight() { + return weight; + } + + public void setWeight(Integer weight) { + this.weight = weight; + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/optimization/CaffeineDemo.java b/JdkLearn/src/main/java/com/learnjava/optimization/CaffeineDemo.java new file mode 100644 index 0000000..88de1a0 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/optimization/CaffeineDemo.java @@ -0,0 +1,131 @@ +package com.learnjava.optimization; + +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.RemovalListener; + +import java.math.BigDecimal; +import java.util.concurrent.TimeUnit; + +/** + * Caffeine 代码Demo(SpringBoot自带的缓存类) + * + * + * @author lhy + * @date 2021/7/22 + */ +public class CaffeineDemo { + + private static Cache productVoCache; + + public static RemovalListener listener = (k, v, cause) -> { + // 业务逻辑 + + + // 触发异常 + switch (cause) { + // 过期 + case EXPIRED: + break; + // 手动删除 + case EXPLICIT: + break; + // 被替换 + case REPLACED: + break; + // 垃圾回收 + case COLLECTED: + break; + // 超过数量限制 + case SIZE: + break; + default: + break; + } + }; + + public static void main(String[] args) { + // 初始化 + // afterPropertiesSet(); + } + + /** + * 模拟Spring的类初始化的时候对缓存进行初始化 + */ + public static void afterPropertiesSet() { + productVoCache = Caffeine.newBuilder() + .softValues() + .refreshAfterWrite(7200, TimeUnit.SECONDS) + .removalListener(listener) + // .build(k -> loadSync(k)) + // 非static类中,可以使用这种方式.build(this::loadSync); + .build(CaffeineDemo::loadSync); + } + + /** + * 获取对应缓存内容 + * @param key + * @return + */ + public static ProductVo getProductVo(String key) { + return productVoCache.get(key, CaffeineDemo::loadSync); + } + + /** + * 对对应商品进行缓存 + * @param key + */ + public static void putProductVo(String key) { + productVoCache.put(key, loadSync(key)); + } + + private static ProductVo loadSync(String key) { + // 业务逻辑 + return new ProductVo(); + } + + + + + + + + + + + + + + + + + + + + + + public static class ProductVo { + + private String productName; + + private BigDecimal price; + + public ProductVo() {} + + public String getProductName() { + return productName; + } + + public void setProductName(String productName) { + this.productName = productName; + } + + public BigDecimal getPrice() { + return price; + } + + public void setPrice(BigDecimal price) { + this.price = price; + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeDemo.java b/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeDemo.java new file mode 100644 index 0000000..fe81a19 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeDemo.java @@ -0,0 +1,70 @@ +package com.learnjava.optimization; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * + * 代码优化技巧总结 + * + * @author lhy + * @date 2021/7/19 + */ +public class OptimizeDemo { + public static void main(String[] args) { + Map map = new HashMap<>(); + mergeData(map); + + Map concurrentMap = new ConcurrentHashMap<>(); + concurrentMergeData(concurrentMap); + } + + /** + * 对于通过map来聚合数据(非Lambda方式) + * @param map + */ + public static void mergeData(Map map) { + String key = "mapKey"; + int value = 1; + // 普通方式 + if (map.containsKey(key)) { + map.put(key, map.get(key) + value); + } else { + map.put(key,value); + } + + // 简洁方式 + Integer mapValue = map.get(key); + if (null != mapValue) { + mapValue += value; + } else { + mapValue = value; + } + map.put(key, mapValue); + } + + /** + * 针对mergeData里map的put操作,在并发情况下会存在put的时候,以及有其他线程已经put成功了,导致线程不安全, + * 所以需要使用并发集合列的putIfAbsent方法 + * @param map + */ + public static void concurrentMergeData(Map map) { + String key = "mapKey"; + int value = 1; + Integer mapValue = map.get(key); + if (null != mapValue) { + mapValue += value; + } else { + mapValue = value; + } + map.putIfAbsent(key, mapValue); + + // computeIfAbsent方法对map中的key只进行重新计算,如果不存在这个key,则添加到map中 + map.computeIfAbsent(key, (k) -> { + // 其他计算 + int a = 1, b = 2; + return a + b; + }); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeUtilDemo.java b/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeUtilDemo.java new file mode 100644 index 0000000..6f131f6 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/optimization/OptimizeUtilDemo.java @@ -0,0 +1,21 @@ +package com.learnjava.optimization; + +/** + * 优化工具类例子 + * + * @author lhy + * @date 2021/7/20 + */ +public class OptimizeUtilDemo { + + /** + * 超快深拷贝工具类BeanCopier + */ + private BeanCopierDemo beanCopierDemo; + + /** + * Caffeine Cache + */ + private CaffeineDemo caffeineDemo; + +} diff --git a/JdkLearn/src/main/java/com/learnjava/proxy/dynamicproxy/DemoInvokerHandler.java b/JdkLearn/src/main/java/com/learnjava/proxy/dynamicproxy/DemoInvokerHandler.java new file mode 100644 index 0000000..c04119e --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/proxy/dynamicproxy/DemoInvokerHandler.java @@ -0,0 +1,53 @@ +package com.learnjava.proxy.dynamicproxy; + +import com.learnjava.proxy.staticproxy.RealSubject; +import com.learnjava.proxy.staticproxy.Subject; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +/** + * @author bruis + * + * jdk动态代理 + * + */ +public class DemoInvokerHandler implements InvocationHandler { + + // 真正的业务对象 + private Object realSubject; + + public DemoInvokerHandler(Object realSubject) { + this.realSubject = realSubject; + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + System.out.println("代理预处理操作"); + + // 调用真正的代理对象逻辑 + Object result = method.invoke(realSubject, args); + + System.out.println("代理后处理操作"); + return result; + } + + /** + * 创建代理对象并返回 + * @return + */ + public Object getProxy() { + return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), + realSubject.getClass().getInterfaces(), this); + } + + public static void main(String[] args) { + RealSubject realSubject = new RealSubject(); + DemoInvokerHandler invokerHandler = new DemoInvokerHandler(realSubject); + Subject proxy = (Subject) invokerHandler.getProxy(); + + // 拿到业务对象,执行业务逻辑,此时业务逻辑已经被代理对象代理了 + proxy.operation(); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/JdkStaticProxy.java b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/JdkStaticProxy.java new file mode 100644 index 0000000..885687d --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/JdkStaticProxy.java @@ -0,0 +1,16 @@ +package com.learnjava.proxy.staticproxy; + +/** + * @author bruis + * + * JDK静态代理 + * + * 特点: + * 1. 静态代理模式需要在编译模式为每个业务类创建代理类Proxy,当需要代理的类很多时,就会出现大量的Proxy类 + * + */ +public class JdkStaticProxy { + + + +} diff --git a/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/RealSubject.java b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/RealSubject.java new file mode 100644 index 0000000..600ccc1 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/RealSubject.java @@ -0,0 +1,10 @@ +package com.learnjava.proxy.staticproxy; + +public class RealSubject implements Subject { + + @Override + public void operation() { + System.out.println("这是真正的业务类"); + } + +} diff --git a/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/Subject.java b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/Subject.java new file mode 100644 index 0000000..d48cf48 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/Subject.java @@ -0,0 +1,5 @@ +package com.learnjava.proxy.staticproxy; + +public interface Subject { + public void operation(); +} diff --git a/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/SubjectProxy.java b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/SubjectProxy.java new file mode 100644 index 0000000..f151e73 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/proxy/staticproxy/SubjectProxy.java @@ -0,0 +1,22 @@ +package com.learnjava.proxy.staticproxy; + +public class SubjectProxy implements Subject { + + private RealSubject realSubject; + + public SubjectProxy() { + realSubject = new RealSubject(); + } + + @Override + public void operation() { + System.out.println("代理预处理逻辑"); + realSubject.operation(); + System.out.println("代理后处理逻辑"); + } + + public static void main(String[] args) { + SubjectProxy proxy = new SubjectProxy(); + proxy.operation(); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/reference/PhantomRefTest.java b/JdkLearn/src/main/java/com/learnjava/reference/PhantomRefTest.java new file mode 100644 index 0000000..426ef67 --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/reference/PhantomRefTest.java @@ -0,0 +1,35 @@ +package com.learnjava.reference; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +public class PhantomRefTest { + + public static void main(String[] args) throws InterruptedException { + ReferenceQueue referenceQueue = new ReferenceQueue(); + + // 10mb + byte[] buffer = new byte[1024 * 1024 * 10]; + + PhantomReference phantomReference = new PhantomReference(buffer, referenceQueue); + + // 字节数组对象,失去了强引用 + buffer = null; + + Reference ref0 = referenceQueue.poll(); + + System.out.println("gc 执行之前, refQueue中是否有数据?" + (ref0 != null ? "有" : "没有")); + System.out.println("gc 执行之前, ref引用的对象:" + phantomReference.get()); + + System.gc(); + // 确保gc程序执行 + Thread.sleep(1000); + + System.out.println("gc 执行之后, ref引用的对象:" + phantomReference.get()); + + Reference ref = referenceQueue.poll(); + System.out.println("gc 执行之后, refQueue中是否有数据?" + (ref != null ? "有" : "没有")); + System.out.println("referenceQueue 中获取的 ref与 weakReference中的是否一致?" + (ref == phantomReference)); + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/reference/SoftReferenceTest.java b/JdkLearn/src/main/java/com/learnjava/reference/SoftReferenceTest.java new file mode 100644 index 0000000..38e706e --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/reference/SoftReferenceTest.java @@ -0,0 +1,35 @@ +package com.learnjava.reference; + +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * 软引用 + */ +public class SoftReferenceTest { + public static void main(String[] args) throws InterruptedException { + List refList = new ArrayList(); + + for (int i = 0; i < 1000; i++) { + // 10mb + byte[] buffer = new byte[1024 * 1024 * 10]; + + SoftReference softReference = new SoftReference(buffer); + + refList.add(softReference); + } + + System.gc(); + Thread.sleep(1000); + + Iterator it = refList.iterator(); + + while (it.hasNext()) { + Reference ref = it.next(); + System.out.println("当前ref引用的对象:" + ref.get()); + } + } +} diff --git a/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java b/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java new file mode 100644 index 0000000..2e2ec2d --- /dev/null +++ b/JdkLearn/src/main/java/com/learnjava/reference/WeakReferenceTest.java @@ -0,0 +1,66 @@ +package com.learnjava.reference; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * soft软引用 gc堆内存不够时,可能会回收 + * weak弱引用 gc时,内存一定会回收不可用对象 + * phantom虚引用 + */ +public class WeakReferenceTest { + public static void main(String[] args) throws Exception { +// test01(); + test02(); + } + + private static void test01() throws InterruptedException { + List refList = new ArrayList(); + + for (int i = 0; i < 1000; i++) { + // 10mb + byte[] buffer = new byte[1024 * 1024 * 10]; + + WeakReference weakReference = new WeakReference(buffer); + + refList.add(weakReference); + } + + // 将buffer直接全部回收了 + System.gc(); + Thread.sleep(1000); + Iterator iterator = refList.iterator(); + while (iterator.hasNext()) { + Reference ref = iterator.next(); + System.out.println("当前ref引用的对象:" + ref.get()); + } + } + + private static void test02() throws InterruptedException { + ReferenceQueue refQueue = new ReferenceQueue(); + + // 10 mb + byte[] buffer = new byte[1024 * 1024 * 10]; + WeakReference weakReference = new WeakReference(buffer, refQueue); + // 失去强引用关联 + buffer = null; + + Reference ref0 = refQueue.poll(); + System.out.println("gc 执行之前, refQueue中是否有数据?" + (ref0 != null ? "有" : "没有")); + System.out.println("gc 执行之前, ref引用的对象:" + weakReference.get()); + + System.gc(); + // 确保gc程序执行 + Thread.sleep(1000); + + System.out.println("gc 执行之后, ref引用的对象:" + weakReference.get()); + + Reference ref = refQueue.poll(); + System.out.println("gc 执行之后, refQueue中是否有数据?" + (ref != null ? "有" : "没有")); + System.out.println("referenceQueue 中获取的 ref与 weakReference中的是否一致?" + (ref == weakReference)); + } +} diff --git a/README.md b/README.md index ca6e8a3..fae4153 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,10 @@ Spring-Security-OAuth2 - Netty + Netty + + + Netty

@@ -43,7 +46,9 @@ Java流行框架源码分析,学习以及总结。项目持续更新中,不 ✅ Dubbo源码 -Netty源码 +✅ Netty源码 + +✅ RocketMQ源码 MyBatis源码 @@ -69,6 +74,8 @@ SpringCloud源码 - [一篇文章快速深入学习ThreadLocal](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/JDK/%E4%B8%80%E7%AF%87%E6%96%87%E7%AB%A0%E5%BF%AB%E9%80%9F%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0ThreadLocal.md) - [深入学习Java volatile关键字](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/JDK/%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0Java%20volatile%E5%85%B3%E9%94%AE%E5%AD%97.md) - [深入学习Thread底层原理](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/JDK/%E6%B7%B1%E5%85%A5%E5%AD%A6%E4%B9%A0Thread%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81.md) + - [深入学习JDK1.7、8 HashMap扩容原理]() + - [开源项目里那些看不懂的位运算分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/JDK/%E5%BC%80%E6%BA%90%E9%A1%B9%E7%9B%AE%E9%87%8C%E9%82%A3%E4%BA%9B%E7%9C%8B%E4%B8%8D%E6%87%82%E7%9A%84%E4%BD%8D%E8%BF%90%E7%AE%97%E5%88%86%E6%9E%90.md) - Spring源码学习 - Spring版本:5.2.1.RELEASE @@ -91,15 +98,9 @@ SpringCloud源码 - [深入浅出SpringBoot源码——SpringFactoriesLoader](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94SpringFactoriesLoader.md) - [深入浅出SpringBoot源码——监听器与事件机制](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%9B%91%E5%90%AC%E5%99%A8%E4%B8%8E%E4%BA%8B%E4%BB%B6%E6%9C%BA%E5%88%B6.md) - - [深入浅出SpringBoot源码——系统初始化器](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%B3%BB%E7%BB%9F.md) + - [深入浅出SpringBoot源码——系统初始化器](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E7%B3%BB%E7%BB%9F%E5%88%9D%E5%A7%8B%E5%8C%96%E5%99%A8.md) - [深入浅出SpringBoot源码——启动加载器](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/SpringBoot/%E6%B7%B1%E5%85%A5SpringBoot%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E4%B9%8B%E2%80%94%E2%80%94%E5%90%AF%E5%8A%A8%E5%8A%A0%E8%BD%BD%E5%99%A8.md) - -- Netty源码学习 - - [二进制运算以及源码、反码以及补码学习](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/%E4%BA%8C%E8%BF%9B%E5%88%B6.md) - - [Netty源码包结构](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E6%BA%90%E7%A0%81%E5%8C%85%E7%BB%93%E6%9E%84.md) - - [Netty中的EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md) - - SpringSecurity&OAuth2源码学习 - SpringSecurity版本:5.1.0.RELEASE - [深入浅出SpringSecurity和OAuth2(一)—— 初识SpringSecurity](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/SpringSecurity/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E7%B3%BB%E7%BB%9F%E5%AD%A6%E4%B9%A0SpringSecurity%E5%92%8COAuth2%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%20%E5%88%9D%E8%AF%86SpringSecurity.md) @@ -109,16 +110,57 @@ SpringCloud源码 - Dubbo底层源码解析 - Dubbo底层源码版本:2.7.8 + - [Dubbo底层源码学习—— 源码搭建](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E6%BA%90%E7%A0%81%E6%90%AD%E5%BB%BA.md) - [Dubbo底层源码学习(一)—— Dubbo的URL](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%B8%80%EF%BC%89%E2%80%94%E2%80%94%20Dubbo%E7%9A%84URL.md) - - [Dubbo底层源码学习(二)—— Dubbo的SPI](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%E2%80%94%E2%80%94%20Dubbo%E7%9A%84SPI%E6%9C%BA%E5%88%B6.md) + - [Dubbo底层源码学习(二)—— Dubbo的SPI机制(上)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%E2%80%94%E2%80%94%20Dubbo%E7%9A%84SPI%E6%9C%BA%E5%88%B6%EF%BC%88%E4%B8%8A%EF%BC%89.md) + - [Dubbo底层源码学习(二)—— Dubbo的SPI机制(中)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo底层源码学习%EF%BC%88二%EF%BC%89——%20Dubbo的SPI机制%EF%BC%88中%EF%BC%89.md +) + - [Dubbo底层源码学习(二)—— Dubbo的SPI机制(下)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%EF%BC%88%E4%BA%8C%EF%BC%89%E2%80%94%E2%80%94%20Dubbo%E7%9A%84SPI%E6%9C%BA%E5%88%B6%EF%BC%88%E4%B8%8B%EF%BC%89.md) - Dubbo底层源码学习(三)—— Dubbo的注册中心 - Dubbo底层源码学习(四)—— Dubbo的注册中心缓存机制 - Dubbo底层源码学习(五)—— Dubbo的注册中心重试机制 - - Dubbo底层源码学习(六)—— Dubbo的服务暴露 + - [Dubbo底层源码学习(六)—— Dubbo的服务暴露](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Dubbo/Dubbo%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E5%AD%A6%E4%B9%A0%E2%80%94%E2%80%94%E6%9C%8D%E5%8A%A1%E6%9A%B4%E9%9C%B2.md) - Dubbo底层源码学习(七)—— Dubbo的服务消费 + +- Netty底层源码解析 + - Netty版本:4.1.43.Final + - [二进制运算以及源码、反码以及补码学习](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/%E4%BA%8C%E8%BF%9B%E5%88%B6.md) + - [Netty源码包结构](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E6%BA%90%E7%A0%81%E5%8C%85%E7%BB%93%E6%9E%84.md) + - [Netty底层源码解析-EventLoopGroup](https://github.com/coderbruis/JavaSourceLearning/blob/master/note/Netty/Netty%E4%B8%AD%E7%9A%84EventLoopGroup%E6%98%AF%E4%BB%80%E4%B9%88.md) + - [Netty底层源码解析-初始Netty及其架构](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-%E5%88%9D%E5%A7%8BNetty%E5%8F%8A%E5%85%B6%E6%9E%B6%E6%9E%84.md) + - [Netty底层源码解析-Netty服务端启动分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-Netty%E6%9C%8D%E5%8A%A1%E7%AB%AF%E5%90%AF%E5%8A%A8%E5%88%86%E6%9E%90.md) + - [Netty底层源码解析-NioEventLoop原理分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-NioEventLoop%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md) + - [Netty底层源码解析-ChannelPipeline分析(上)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8A%EF%BC%89.md) + - [Netty底层源码解析-ChannelPipeline分析(下)](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-ChannelPipeline%E5%88%86%E6%9E%90%EF%BC%88%E4%B8%8B%EF%BC%89.md) + - [Netty底层源码解析-NioServerSocketChannel接受数据原理分析](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/Netty/Netty%E5%BA%95%E5%B1%82%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90-NioServerSocketChannel%E6%8E%A5%E5%8F%97%E6%95%B0%E6%8D%AE%E5%8E%9F%E7%90%86%E5%88%86%E6%9E%90.md) + - Netty底层源码解析-NioSocketChannel接受、发送数据原理分析 + - Netty底层源码解析-FastThreadLocal原理分析 + - Netty底层源码解析-内存分配原理分析 + - Netty底层源码解析-RocketMQ底层使用到的Netty + - [Netty底层的优化总结]() + - [实战+原理效果更佳!强烈推荐闪电侠大佬实战课:《Netty 入门与实战:仿写微信 IM 即时通讯系统》](https://juejin.cn/book/6844733738119593991) + +Netty实战课相关点位于:Spring-Netty,com/bruis/learnnetty/im包下,有需要的读者可前往查看。 + + +- RocketMQ底层源码解析 + - RocketMQ版本:4.9.0 + - RocketMQ底层源码解析-RocketMQ环境搭建 + - RocketMQ底层源码解析-本地调试RocketMQ源码 + - RocketMQ底层源码解析-NameServer分析 持续更新中... - + +todo + +2021年年底完成了人生的两件大事,所以一直没时间持续输出源码分析,2022年开始需要继续努力,继续完成这个源码分析项目! + +- 完成Netty剩余源码分析文章 +- 完成RocketMQ剩余源码分析文章 +- 完成Dubbo剩余源码分析文章 +- C语言基础学习(为Redis底层源码学习做准备) +- Redis底层源码分析 +- JUC底层源码分析 # 支持 diff --git a/Spring-Netty/pom.xml b/Spring-Netty/pom.xml index 3cfc328..82d7684 100644 --- a/Spring-Netty/pom.xml +++ b/Spring-Netty/pom.xml @@ -16,7 +16,6 @@ 1.8 - 4.1.42.Final @@ -35,7 +34,12 @@ io.netty netty-all - ${netty-all.version} + + + + com.alibaba + fastjson + 1.2.76 diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java new file mode 100644 index 0000000..4a701b4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/NettyClient.java @@ -0,0 +1,106 @@ +package com.bruis.learnnetty.im.client; + +import com.bruis.learnnetty.im.client.handler.*; +import com.bruis.learnnetty.im.codec.PacketDecoder; +import com.bruis.learnnetty.im.codec.PacketEncoder; +import com.bruis.learnnetty.im.codec.Spliter; +import com.bruis.learnnetty.im.console.ConsoleCommandManager; +import com.bruis.learnnetty.im.console.LoginConsoleCommand; +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.Date; +import java.util.Scanner; +import java.util.concurrent.TimeUnit; + +/** + * @Description 客户端 + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class NettyClient { + private static final int MAX_RETRY = 5; + private static final String HOST = "127.0.0.1"; + private static final int PORT = 8000; + + + public static void main(String[] args) { + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + + Bootstrap bootstrap = new Bootstrap(); + bootstrap + .group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .handler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + // 拆包粘包处理 + ch.pipeline().addLast(new Spliter()); + // 编码 + ch.pipeline().addLast(new PacketDecoder()); + // 登录响应 + ch.pipeline().addLast(new LoginResponseHandler()); + // 消息返回 + ch.pipeline().addLast(new MessageResponseHandler()); + ch.pipeline().addLast(new CreateGroupResponseHandler()); + ch.pipeline().addLast(new JoinGroupResponseHandler()); + ch.pipeline().addLast(new QuitGroupResponseHandler()); + ch.pipeline().addLast(new ListGroupMembersResponseHandler()); + ch.pipeline().addLast(new GroupMessageResponseHandler()); + ch.pipeline().addLast(new LogoutResponseHandler()); + // 解码 + ch.pipeline().addLast(new PacketEncoder()); + } + }); + + connect(bootstrap, HOST, PORT, MAX_RETRY); + } + + private static void connect(Bootstrap bootstrap, String host, int port, int retry) { + bootstrap.connect(host, port).addListener(future -> { + if (future.isSuccess()) { + System.out.println(new Date() + ": 连接成功,启动控制台线程……"); + Channel channel = ((ChannelFuture) future).channel(); + startConsoleThread(channel); + } else if (retry == 0) { + System.err.println("重试次数已用完,放弃连接!"); + } else { + // 第几次重连 + int order = (MAX_RETRY - retry) + 1; + // 本次重连的间隔 + int delay = 1 << order; + System.err.println(new Date() + ": 连接失败,第" + order + "次重连……"); + bootstrap.config().group().schedule(() -> connect(bootstrap, host, port, retry - 1), delay, TimeUnit + .SECONDS); + } + }); + } + + private static void startConsoleThread(Channel channel) { + ConsoleCommandManager consoleCommandManager = new ConsoleCommandManager(); + LoginConsoleCommand loginConsoleCommand = new LoginConsoleCommand(); + Scanner scanner = new Scanner(System.in); + + new Thread(() -> { + while (!Thread.interrupted()) { + if (!SessionUtil.hasLogin(channel)) { + loginConsoleCommand.exec(scanner, channel); + } else { + consoleCommandManager.exec(scanner, channel); + } + } + }).start(); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java new file mode 100644 index 0000000..04125de --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/CreateGroupResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.CreateGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, CreateGroupResponsePacket msg) throws Exception { + System.out.print("群创建成功,id 为[" + msg.getGroupId() + "], "); + System.out.println("群里面有:" + msg.getUserNameList()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java new file mode 100644 index 0000000..3dc9921 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/GroupMessageResponseHandler.java @@ -0,0 +1,20 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.GroupMessageResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, GroupMessageResponsePacket responsePacket) { + String fromGroupId = responsePacket.getFromGroupId(); + Session fromUser = responsePacket.getFromUser(); + System.out.println("收到群[" + fromGroupId + "]中[" + fromUser + "]发来的消息:" + responsePacket.getMessage()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java new file mode 100644 index 0000000..cc7efea --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/JoinGroupResponseHandler.java @@ -0,0 +1,21 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.JoinGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, JoinGroupResponsePacket responsePacket) throws Exception { + if (responsePacket.isSuccess()) { + System.out.println("加入群[" + responsePacket.getGroupId() + "]成功!"); + } else { + System.err.println("加入群[" + responsePacket.getGroupId() + "]失败,原因为:" + responsePacket.getReason()); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java new file mode 100644 index 0000000..0117eac --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/ListGroupMembersResponseHandler.java @@ -0,0 +1,18 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.ListGroupMembersResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ListGroupMembersResponsePacket responsePacket) { + System.out.println("群[" + responsePacket.getGroupId() + "]中的人包括:" + responsePacket.getSessionList()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java new file mode 100644 index 0000000..282afcc --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LoginResponseHandler.java @@ -0,0 +1,33 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.LoginResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description 登录响应的reponse + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LoginResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LoginResponsePacket loginResponsePacket) throws Exception { + String userId = loginResponsePacket.getUserId(); + String userName = loginResponsePacket.getUserName(); + + if (loginResponsePacket.isSuccess()) { + System.out.println("[" + userName + "]登录成功,userId 为: " + loginResponsePacket.getUserId()); + SessionUtil.bindSession(new Session(userId, userName), ctx.channel()); + } else { + System.out.println("[" + userName + "]登录失败,原因:" + loginResponsePacket.getReason()); + } + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) throws Exception { + System.out.println("客户端连接被关闭"); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java new file mode 100644 index 0000000..78fd173 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/LogoutResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.LogoutResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LogoutResponsePacket logoutResponsePacket) { + SessionUtil.unBindSession(ctx.channel()); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java new file mode 100644 index 0000000..ea6fac1 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/MessageResponseHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.MessageResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class MessageResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageResponsePacket messageResponsePacket) throws Exception { + String fromUserId = messageResponsePacket.getFromUserId(); + String fromUserName = messageResponsePacket.getFromUserName(); + System.out.println(fromUserId + ":" + fromUserName + " -> " + messageResponsePacket.getMessage()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java new file mode 100644 index 0000000..be82bd5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/client/handler/QuitGroupResponseHandler.java @@ -0,0 +1,22 @@ +package com.bruis.learnnetty.im.client.handler; + +import com.bruis.learnnetty.im.model.QuitGroupResponsePacket; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupResponseHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, QuitGroupResponsePacket responsePacket) throws Exception { + if (responsePacket.isSuccess()) { + System.out.println("退出群聊[" + responsePacket.getGroupId() + "]成功!"); + } else { + System.out.println("退出群聊[" + responsePacket.getGroupId() + "]失败!"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java new file mode 100644 index 0000000..35aa573 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketCodecHandler.java @@ -0,0 +1,35 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.Packet; +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageCodec; + +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/25 + */ +@ChannelHandler.Sharable +public class PacketCodecHandler extends MessageToMessageCodec { + + public static final PacketCodecHandler INSTANCE = new PacketCodecHandler(); + + private PacketCodecHandler() {} + + @Override + protected void encode(ChannelHandlerContext ctx, Packet msg, List out) throws Exception { + ByteBuf byteBuf = ctx.channel().alloc().ioBuffer(); + PacketCodeC.INSTANCE.encode(byteBuf, msg); + out.add(byteBuf); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { + out.add(PacketCodeC.INSTANCE.decode(msg)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java new file mode 100644 index 0000000..751d007 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketDecoder.java @@ -0,0 +1,20 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class PacketDecoder extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List out) throws Exception { + out.add(PacketCodeC.INSTANCE.decode(byteBuf)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java new file mode 100644 index 0000000..d3d4fa7 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/PacketEncoder.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.Packet; +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class PacketEncoder extends MessageToByteEncoder { + @Override + protected void encode(ChannelHandlerContext channelHandlerContext, Packet packet, ByteBuf byteBuf) throws Exception { + PacketCodeC.INSTANCE.encode(byteBuf, packet); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java new file mode 100644 index 0000000..c09b096 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/codec/Spliter.java @@ -0,0 +1,30 @@ +package com.bruis.learnnetty.im.codec; + +import com.bruis.learnnetty.im.model.PacketCodeC; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +/** + * @Description 拆包、粘包处理 + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class Spliter extends LengthFieldBasedFrameDecoder { + private static final int LENGTH_FIELD_OFFSET = 7; + private static final int LENGTH_FIELD_LENGTH = 4; + + public Spliter() { + super(Integer.MAX_VALUE, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + // 校验协议 + if (in.getInt(in.readerIndex()) != PacketCodeC.MAGIC_NUMBER) { + ctx.channel().close(); + return null; + } + return super.decode(ctx, in); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java new file mode 100644 index 0000000..dd41e27 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommand.java @@ -0,0 +1,14 @@ +package com.bruis.learnnetty.im.console; + +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description 指令接口 + * @Author luohaiyang + * @Date 2022/3/23 + */ +public interface ConsoleCommand { + void exec(Scanner scanner, Channel channel); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java new file mode 100644 index 0000000..8bf69f6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ConsoleCommandManager.java @@ -0,0 +1,43 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; + +import java.util.HashMap; +import java.util.Map; +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class ConsoleCommandManager implements ConsoleCommand { + + private Map consoleCommandMap; + + public ConsoleCommandManager() { + consoleCommandMap = new HashMap<>(); + consoleCommandMap.put("sendToUser", new SendToUserConsoleCommand()); + consoleCommandMap.put("logout", new LogoutConsoleCommand()); + consoleCommandMap.put("createGroup", new CreateGroupConsoleCommand()); + consoleCommandMap.put("joinGroup", new JoinGroupConsoleCommand()); + consoleCommandMap.put("quitGroup", new QuitGroupConsoleCommand()); + consoleCommandMap.put("listGroup", new ListGroupMembersConsoleCommand()); + consoleCommandMap.put("sendToGroup", new SendToGroupConsoleCommand()); + } + + @Override + public void exec(Scanner scanner, Channel channel) { + String command = scanner.next(); + if (!SessionUtil.hasLogin(channel)) { + return; + } + ConsoleCommand consoleCommand = consoleCommandMap.get(command); + if (null != consoleCommand) { + consoleCommand.exec(scanner, channel); + } else { + System.err.println("无法识别[" + command + "]指令,请重新输入"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java new file mode 100644 index 0000000..db68b98 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/CreateGroupConsoleCommand.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.CreateGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Arrays; +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupConsoleCommand implements ConsoleCommand { + + private static final String USER_ID_SPLITER = ","; + + @Override + public void exec(Scanner scanner, Channel channel) { + CreateGroupRequestPacket createGroupRequestPacket = new CreateGroupRequestPacket(); + + System.out.print("【拉人群聊】输入 userId 列表,userId 之间英文逗号隔开:"); + String userIds = scanner.next(); + createGroupRequestPacket.setUserIdList(Arrays.asList(userIds.split(USER_ID_SPLITER))); + channel.writeAndFlush(createGroupRequestPacket); + } + +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java new file mode 100644 index 0000000..c3ff3f4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/JoinGroupConsoleCommand.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.JoinGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupConsoleCommand implements ConsoleCommand{ + + @Override + public void exec(Scanner scanner, Channel channel) { + JoinGroupRequestPacket requestPacket = new JoinGroupRequestPacket(); + System.out.println("输入groupId, 加入群聊:"); + String groupId = scanner.next(); + requestPacket.setGroupId(groupId); + channel.writeAndFlush(requestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java new file mode 100644 index 0000000..87e79c4 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/ListGroupMembersConsoleCommand.java @@ -0,0 +1,25 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.ListGroupMembersRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + ListGroupMembersRequestPacket listGroupMembersRequestPacket = new ListGroupMembersRequestPacket(); + + System.out.print("输入 groupId,获取群成员列表:"); + String groupId = scanner.next(); + + listGroupMembersRequestPacket.setGroupId(groupId); + channel.writeAndFlush(listGroupMembersRequestPacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java new file mode 100644 index 0000000..3e632ca --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LoginConsoleCommand.java @@ -0,0 +1,38 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LoginConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + LoginRequestPacket loginRequestPacket = new LoginRequestPacket(); + + System.out.print("输入用户名登录: "); + String userIdStr; // 在退出登录logout之后 这里会读取到最后一个回车符 用户名就是空字符串会导致无法退出登录 + while ((userIdStr = scanner.nextLine()).isEmpty()) { + System.out.println("用户名异常, 请重新输入"); + } + loginRequestPacket.setUserName(userIdStr); + loginRequestPacket.setPassword("pwd"); + + // 发送登录数据包 + channel.writeAndFlush(loginRequestPacket); + waitForLoginResponse(); + } + + private static void waitForLoginResponse() { + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java new file mode 100644 index 0000000..2d13370 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/LogoutConsoleCommand.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.LogoutRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutConsoleCommand implements ConsoleCommand { + @Override + public void exec(Scanner scanner, Channel channel) { + LogoutRequestPacket logoutRequestPacket = new LogoutRequestPacket(); + channel.writeAndFlush(logoutRequestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java new file mode 100644 index 0000000..4b4b284 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/QuitGroupConsoleCommand.java @@ -0,0 +1,25 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.QuitGroupRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + QuitGroupRequestPacket quitGroupRequestPacket = new QuitGroupRequestPacket(); + + System.out.print("输入 groupId,退出群聊:"); + String groupId = scanner.next(); + + quitGroupRequestPacket.setGroupId(groupId); + channel.writeAndFlush(quitGroupRequestPacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java new file mode 100644 index 0000000..90f8f90 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToGroupConsoleCommand.java @@ -0,0 +1,24 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.GroupMessageRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class SendToGroupConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + System.out.print("发送消息给某个某个群组:"); + + String toGroupId = scanner.next(); + String message = scanner.next(); + channel.writeAndFlush(new GroupMessageRequestPacket(toGroupId, message)); + + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java new file mode 100644 index 0000000..9ead2b5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/console/SendToUserConsoleCommand.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.im.console; + +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import io.netty.channel.Channel; + +import java.util.Scanner; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class SendToUserConsoleCommand implements ConsoleCommand { + + @Override + public void exec(Scanner scanner, Channel channel) { + System.out.print("发送消息给某个某个用户:"); + + String toUserId = scanner.next(); + String message = scanner.next(); + channel.writeAndFlush(new MessageRequestPacket(toUserId, message)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java new file mode 100644 index 0000000..fe72853 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Attributes.java @@ -0,0 +1,13 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; +import io.netty.util.AttributeKey; + +/** + * @Description Netty 属性集 + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Attributes { + AttributeKey SESSION = AttributeKey.newInstance("session"); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java new file mode 100644 index 0000000..f84f29f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Command.java @@ -0,0 +1,44 @@ +package com.bruis.learnnetty.im.model; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Command { + Byte LOGIN_REQUEST = 1; + + Byte LOGIN_RESPONSE = 2; + + Byte MESSAGE_REQUEST = 3; + + Byte MESSAGE_RESPONSE = 4; + + Byte LOGOUT_REQUEST = 5; + + Byte LOGOUT_RESPONSE = 6; + + Byte CREATE_GROUP_REQUEST = 7; + + Byte CREATE_GROUP_RESPONSE = 8; + + Byte LIST_GROUP_MEMBERS_REQUEST = 9; + + Byte LIST_GROUP_MEMBERS_RESPONSE = 10; + + Byte JOIN_GROUP_REQUEST = 11; + + Byte JOIN_GROUP_RESPONSE = 12; + + Byte QUIT_GROUP_REQUEST = 13; + + Byte QUIT_GROUP_RESPONSE = 14; + + Byte GROUP_MESSAGE_REQUEST = 15; + + Byte GROUP_MESSAGE_RESPONSE = 16; + + Byte HEARTBEAT_REQUEST = 17; + + Byte HEARTBEAT_RESPONSE = 18; +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java new file mode 100644 index 0000000..dc9ffc5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupRequestPacket.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.im.model; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.CREATE_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupRequestPacket extends Packet { + + private List userIdList; + + @Override + public Byte getCommand() { + return CREATE_GROUP_REQUEST; + } + + public List getUserIdList() { + return userIdList; + } + + public void setUserIdList(List userIdList) { + this.userIdList = userIdList; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java new file mode 100644 index 0000000..6209205 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/CreateGroupResponsePacket.java @@ -0,0 +1,48 @@ +package com.bruis.learnnetty.im.model; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.CREATE_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class CreateGroupResponsePacket extends Packet { + private boolean success; + + private String groupId; + + private List userNameList; + + @Override + public Byte getCommand() { + + return CREATE_GROUP_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public List getUserNameList() { + return userNameList; + } + + public void setUserNameList(List userNameList) { + this.userNameList = userNameList; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java new file mode 100644 index 0000000..3a6f812 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageRequestPacket.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.GROUP_MESSAGE_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageRequestPacket extends Packet { + private String toGroupId; + private String message; + + public GroupMessageRequestPacket(String toGroupId, String message) { + this.toGroupId = toGroupId; + this.message = message; + } + + @Override + public Byte getCommand() { + return GROUP_MESSAGE_REQUEST; + } + + public String getToGroupId() { + return toGroupId; + } + + public void setToGroupId(String toGroupId) { + this.toGroupId = toGroupId; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java new file mode 100644 index 0000000..986333b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/GroupMessageResponsePacket.java @@ -0,0 +1,49 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; + +import static com.bruis.learnnetty.im.model.Command.GROUP_MESSAGE_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class GroupMessageResponsePacket extends Packet { + + private String fromGroupId; + + private Session fromUser; + + private String message; + + @Override + public Byte getCommand() { + + return GROUP_MESSAGE_RESPONSE; + } + + public String getFromGroupId() { + return fromGroupId; + } + + public void setFromGroupId(String fromGroupId) { + this.fromGroupId = fromGroupId; + } + + public Session getFromUser() { + return fromUser; + } + + public void setFromUser(Session fromUser) { + this.fromUser = fromUser; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java new file mode 100644 index 0000000..26fb73d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupRequestPacket.java @@ -0,0 +1,26 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.JOIN_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + return JOIN_GROUP_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java new file mode 100644 index 0000000..dce9a1d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/JoinGroupResponsePacket.java @@ -0,0 +1,46 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.JOIN_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class JoinGroupResponsePacket extends Packet { + + private String groupId; + + private boolean success; + + private String reason; + + @Override + public Byte getCommand() { + return JOIN_GROUP_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java new file mode 100644 index 0000000..886b19c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersRequestPacket.java @@ -0,0 +1,27 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LIST_GROUP_MEMBERS_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + + return LIST_GROUP_MEMBERS_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java new file mode 100644 index 0000000..dbc174e --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/ListGroupMembersResponsePacket.java @@ -0,0 +1,41 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.session.Session; + +import java.util.List; + +import static com.bruis.learnnetty.im.model.Command.LIST_GROUP_MEMBERS_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class ListGroupMembersResponsePacket extends Packet { + + private String groupId; + + private List sessionList; + + @Override + public Byte getCommand() { + + return LIST_GROUP_MEMBERS_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public List getSessionList() { + return sessionList; + } + + public void setSessionList(List sessionList) { + this.sessionList = sessionList; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java new file mode 100644 index 0000000..d1122bd --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginRequestPacket.java @@ -0,0 +1,46 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGIN_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class LoginRequestPacket extends Packet { + + private String userId; + + private String userName; + + private String password; + + @Override + public Byte getCommand() { + return LOGIN_REQUEST; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java new file mode 100644 index 0000000..32599fd --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LoginResponsePacket.java @@ -0,0 +1,57 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGIN_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class LoginResponsePacket extends Packet { + + private String userId; + + private String userName; + + private boolean success; + + private String reason; + + + @Override + public Byte getCommand() { + return LOGIN_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java new file mode 100644 index 0000000..c66dc68 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutRequestPacket.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGOUT_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class LogoutRequestPacket extends Packet { + @Override + public Byte getCommand() { + + return LOGOUT_REQUEST; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java new file mode 100644 index 0000000..73d2a71 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/LogoutResponsePacket.java @@ -0,0 +1,37 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.LOGOUT_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class LogoutResponsePacket extends Packet { + + private boolean success; + + private String reason; + + + @Override + public Byte getCommand() { + return LOGOUT_RESPONSE; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java new file mode 100644 index 0000000..4dae50c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageRequestPacket.java @@ -0,0 +1,43 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.MESSAGE_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class MessageRequestPacket extends Packet { + + private String toUserId; + + private String message; + + public MessageRequestPacket(){} + + public MessageRequestPacket(String toUserId, String message) { + this.toUserId = toUserId; + this.message = message; + } + + @Override + public Byte getCommand() { + return MESSAGE_REQUEST; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getToUserId() { + return toUserId; + } + + public void setToUserId(String toUserId) { + this.toUserId = toUserId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java new file mode 100644 index 0000000..372a33b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/MessageResponsePacket.java @@ -0,0 +1,47 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.MESSAGE_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class MessageResponsePacket extends Packet { + + private String fromUserId; + + private String fromUserName; + + private String message; + + @Override + public Byte getCommand() { + + return MESSAGE_RESPONSE; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getFromUserId() { + return fromUserId; + } + + public void setFromUserId(String fromUserId) { + this.fromUserId = fromUserId; + } + + public String getFromUserName() { + return fromUserName; + } + + public void setFromUserName(String fromUserName) { + this.fromUserName = fromUserName; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java new file mode 100644 index 0000000..0100f52 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/Packet.java @@ -0,0 +1,27 @@ +package com.bruis.learnnetty.im.model; + +import com.alibaba.fastjson.annotation.JSONField; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public abstract class Packet { + /** + * 协议版本 + */ + @JSONField(deserialize = false , serialize = false) + private Byte version = 1; + + @JSONField(serialize = false) + public abstract Byte getCommand(); + + public Byte getVersion() { + return version; + } + + public void setVersion(Byte version) { + this.version = version; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java new file mode 100644 index 0000000..7a81483 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/PacketCodeC.java @@ -0,0 +1,102 @@ +package com.bruis.learnnetty.im.model; + +import com.bruis.learnnetty.im.serialize.Serializer; +import com.bruis.learnnetty.im.serialize.impl.JSONSerializer; +import io.netty.buffer.ByteBuf; + +import java.util.HashMap; +import java.util.Map; + +import static com.bruis.learnnetty.im.model.Command.*; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class PacketCodeC { + public static final int MAGIC_NUMBER = 0x12345678; + public static final PacketCodeC INSTANCE = new PacketCodeC(); + + private final Map> packetTypeMap; + private final Map serializerMap; + + + private PacketCodeC() { + packetTypeMap = new HashMap<>(); + packetTypeMap.put(LOGIN_REQUEST, LoginRequestPacket.class); + packetTypeMap.put(LOGIN_RESPONSE, LoginResponsePacket.class); + packetTypeMap.put(MESSAGE_REQUEST, MessageRequestPacket.class); + packetTypeMap.put(MESSAGE_RESPONSE, MessageResponsePacket.class); + packetTypeMap.put(LOGOUT_REQUEST, LogoutRequestPacket.class); + packetTypeMap.put(LOGOUT_RESPONSE, LogoutResponsePacket.class); + packetTypeMap.put(CREATE_GROUP_REQUEST, CreateGroupRequestPacket.class); + packetTypeMap.put(CREATE_GROUP_RESPONSE, CreateGroupResponsePacket.class); + packetTypeMap.put(JOIN_GROUP_REQUEST, JoinGroupRequestPacket.class); + packetTypeMap.put(JOIN_GROUP_RESPONSE, JoinGroupResponsePacket.class); + packetTypeMap.put(QUIT_GROUP_REQUEST, QuitGroupRequestPacket.class); + packetTypeMap.put(QUIT_GROUP_RESPONSE, QuitGroupResponsePacket.class); + packetTypeMap.put(LIST_GROUP_MEMBERS_REQUEST, ListGroupMembersRequestPacket.class); + packetTypeMap.put(LIST_GROUP_MEMBERS_RESPONSE, ListGroupMembersResponsePacket.class); + packetTypeMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequestPacket.class); + packetTypeMap.put(GROUP_MESSAGE_RESPONSE, GroupMessageResponsePacket.class); + + serializerMap = new HashMap<>(); + Serializer serializer = new JSONSerializer(); + serializerMap.put(serializer.getSerializerAlogrithm(), serializer); + } + + + public void encode(ByteBuf byteBuf, Packet packet) { + // 1. 序列化 java 对象 + byte[] bytes = Serializer.DEFAULT.serialize(packet); + + // 2. 实际编码过程 + byteBuf.writeInt(MAGIC_NUMBER); + byteBuf.writeByte(packet.getVersion()); + byteBuf.writeByte(Serializer.DEFAULT.getSerializerAlogrithm()); + byteBuf.writeByte(packet.getCommand()); + byteBuf.writeInt(bytes.length); + byteBuf.writeBytes(bytes); + } + + + public Packet decode(ByteBuf byteBuf) { + // 跳过 magic number + byteBuf.skipBytes(4); + + // 跳过版本号 + byteBuf.skipBytes(1); + + // 序列化算法 + byte serializeAlgorithm = byteBuf.readByte(); + + // 指令 + byte command = byteBuf.readByte(); + + // 数据包长度 + int length = byteBuf.readInt(); + + byte[] bytes = new byte[length]; + byteBuf.readBytes(bytes); + + Class requestType = getRequestType(command); + Serializer serializer = getSerializer(serializeAlgorithm); + + if (requestType != null && serializer != null) { + return serializer.deserialize(requestType, bytes); + } + + return null; + } + + private Serializer getSerializer(byte serializeAlgorithm) { + + return serializerMap.get(serializeAlgorithm); + } + + private Class getRequestType(byte command) { + + return packetTypeMap.get(command); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java new file mode 100644 index 0000000..ca5342f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupRequestPacket.java @@ -0,0 +1,26 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_REQUEST; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupRequestPacket extends Packet { + + private String groupId; + + @Override + public Byte getCommand() { + return QUIT_GROUP_REQUEST; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java new file mode 100644 index 0000000..99529c7 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/model/QuitGroupResponsePacket.java @@ -0,0 +1,47 @@ +package com.bruis.learnnetty.im.model; + +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_REQUEST; +import static com.bruis.learnnetty.im.model.Command.QUIT_GROUP_RESPONSE; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class QuitGroupResponsePacket extends Packet { + + private String groupId; + + private boolean success; + + private String reason; + + @Override + public Byte getCommand() { + return QUIT_GROUP_RESPONSE; + } + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getReason() { + return reason; + } + + public void setReason(String reason) { + this.reason = reason; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java new file mode 100644 index 0000000..1bce9c9 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/Serializer.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.serialize; + + +import com.bruis.learnnetty.im.serialize.impl.JSONSerializer; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface Serializer { + Serializer DEFAULT = new JSONSerializer(); + + /** + * 序列化算法 + * @return + */ + byte getSerializerAlogrithm(); + + /** + * java 对象转换成二进制 + */ + byte[] serialize(Object object); + + /** + * 二进制转换成 java 对象 + */ + T deserialize(Class clazz, byte[] bytes); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java new file mode 100644 index 0000000..ef50887 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/SerializerAlogrithm.java @@ -0,0 +1,13 @@ +package com.bruis.learnnetty.im.serialize; + +/** + * @Description + * @Author haiyangluo + * @Date 2022/3/22 + */ +public interface SerializerAlogrithm { + /** + * json 序列化 + */ + byte JSON = 1; +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java new file mode 100644 index 0000000..9a2881f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/serialize/impl/JSONSerializer.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.serialize.impl; + +import com.alibaba.fastjson.JSON; +import com.bruis.learnnetty.im.serialize.Serializer; +import com.bruis.learnnetty.im.serialize.SerializerAlogrithm; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class JSONSerializer implements Serializer { + @Override + public byte getSerializerAlogrithm() { + return SerializerAlogrithm.JSON; + } + + @Override + public byte[] serialize(Object object) { + + return JSON.toJSONBytes(object); + } + + @Override + public T deserialize(Class clazz, byte[] bytes) { + + return JSON.parseObject(bytes, clazz); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java new file mode 100644 index 0000000..08d3f14 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/NettyServer.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.server; + +import com.bruis.learnnetty.im.codec.PacketCodecHandler; +import com.bruis.learnnetty.im.codec.PacketDecoder; +import com.bruis.learnnetty.im.codec.PacketEncoder; +import com.bruis.learnnetty.im.codec.Spliter; +import com.bruis.learnnetty.im.server.handler.*; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +import java.util.Date; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/22 + */ +public class NettyServer { + + private static final int PORT = 8000; + + public static void main(String[] args) { + NioEventLoopGroup bossGroup = new NioEventLoopGroup(); + NioEventLoopGroup workerGroup = new NioEventLoopGroup(); + + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 1024) + .childOption(ChannelOption.SO_KEEPALIVE, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new Spliter()); + ch.pipeline().addLast(PacketCodecHandler.INSTANCE); + ch.pipeline().addLast(LoginRequestHandler.INSTANCE); + ch.pipeline().addLast(AuthHandler.INSTANCE); + ch.pipeline().addLast(IMHandler.INSTANCE); + } + }); + bind(serverBootstrap, PORT); + } + + private static void bind(final ServerBootstrap serverBootstrap, final int port) { + serverBootstrap.bind(port).addListener(future -> { + if (future.isSuccess()) { + System.out.println(new Date() + ": 端口[" + port + "]绑定成功!"); + } else { + System.err.println("端口[" + port + "]绑定失败!"); + } + }); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java new file mode 100644 index 0000000..ad5c784 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/AuthHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class AuthHandler extends ChannelInboundHandlerAdapter { + + public static final AuthHandler INSTANCE = new AuthHandler(); + + protected AuthHandler() {} + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + if (!SessionUtil.hasLogin(ctx.channel())) { + ctx.channel().close(); + } else { + ctx.pipeline().remove(this); + super.channelRead(ctx, msg); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java new file mode 100644 index 0000000..64c57cf --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/CreateGroupRequestHandler.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.CreateGroupRequestPacket; +import com.bruis.learnnetty.im.model.CreateGroupResponsePacket; +import com.bruis.learnnetty.im.util.IDUtil; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import org.omg.PortableServer.ID_UNIQUENESS_POLICY_ID; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class CreateGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final CreateGroupRequestHandler INSTANCE = new CreateGroupRequestHandler(); + + protected CreateGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, CreateGroupRequestPacket msg) throws Exception { + List userIdList = msg.getUserIdList(); + List userNameList = new ArrayList<>(); + + ChannelGroup channelGroup = new DefaultChannelGroup(ctx.executor()); + + for (String userId : userIdList) { + Channel channel = SessionUtil.getChannel(userId); + if (null != channel) { + channelGroup.add(channel); + userNameList.add(SessionUtil.getSession(channel).getUserName()); + } + } + + String groupId = IDUtil.randomUserId(); + CreateGroupResponsePacket createGroupResponsePacket = new CreateGroupResponsePacket(); + createGroupResponsePacket.setSuccess(true); + createGroupResponsePacket.setGroupId(groupId); + createGroupResponsePacket.setUserNameList(userNameList); + + channelGroup.writeAndFlush(createGroupResponsePacket); + + System.out.print("群创建成功,id 为[" + createGroupResponsePacket.getGroupId() + "], "); + System.out.println("群里面有:" + createGroupResponsePacket.getUserNameList()); + + SessionUtil.bindChannelGroup(groupId, channelGroup); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java new file mode 100644 index 0000000..5507f45 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/GroupMessageRequestHandler.java @@ -0,0 +1,34 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.GroupMessageRequestPacket; +import com.bruis.learnnetty.im.model.GroupMessageResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class GroupMessageRequestHandler extends SimpleChannelInboundHandler { + + public static final GroupMessageRequestHandler INSTANCE = new GroupMessageRequestHandler(); + + public GroupMessageRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, GroupMessageRequestPacket msg) throws Exception { + String toGroupId = msg.getToGroupId(); + GroupMessageResponsePacket responsePacket = new GroupMessageResponsePacket(); + responsePacket.setFromGroupId(toGroupId); + responsePacket.setMessage(msg.getMessage()); + responsePacket.setFromUser(SessionUtil.getSession(ctx.channel())); + + ChannelGroup channelGroup = SessionUtil.getChannelGroup(toGroupId); + channelGroup.writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java new file mode 100644 index 0000000..8420f57 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/IMHandler.java @@ -0,0 +1,42 @@ +package com.bruis.learnnetty.im.server.handler; + +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import com.bruis.learnnetty.im.model.Packet; + +import java.util.HashMap; +import java.util.Map; + +import static com.bruis.learnnetty.im.model.Command.*; + + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/25 + */ +@ChannelHandler.Sharable +public class IMHandler extends SimpleChannelInboundHandler { + + public static final IMHandler INSTANCE = new IMHandler(); + + private Map> handlerMap; + + private IMHandler() { + handlerMap = new HashMap<>(); + + handlerMap.put(MESSAGE_REQUEST, MessageRequestHandler.INSTANCE); + handlerMap.put(CREATE_GROUP_REQUEST, CreateGroupRequestHandler.INSTANCE); + handlerMap.put(JOIN_GROUP_REQUEST, JoinGroupRequestHandler.INSTANCE); + handlerMap.put(QUIT_GROUP_REQUEST, QuitGroupRequestHandler.INSTANCE); + handlerMap.put(LIST_GROUP_MEMBERS_REQUEST, ListGroupMembersRequestHandler.INSTANCE); + handlerMap.put(GROUP_MESSAGE_REQUEST, GroupMessageRequestHandler.INSTANCE); + handlerMap.put(LOGOUT_REQUEST, LogoutRequestHandler.INSTANCE); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, Packet packet) throws Exception { + handlerMap.get(packet.getCommand()).channelRead(ctx, packet); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java new file mode 100644 index 0000000..1188ec6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/JoinGroupRequestHandler.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.JoinGroupRequestPacket; +import com.bruis.learnnetty.im.model.JoinGroupResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class JoinGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final JoinGroupRequestHandler INSTANCE = new JoinGroupRequestHandler(); + + protected JoinGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, JoinGroupRequestPacket msg) throws Exception { + // 目标群聊id + String groupId = msg.getGroupId(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + JoinGroupResponsePacket responsePacket = new JoinGroupResponsePacket(); + responsePacket.setSuccess(true); + responsePacket.setGroupId(groupId); + if (null == channelGroup) { + responsePacket.setSuccess(false); + responsePacket.setReason("没有该群聊,请重试..."); + } else { + channelGroup.add(ctx.channel()); + } + ctx.channel().writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java new file mode 100644 index 0000000..8be361d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/ListGroupMembersRequestHandler.java @@ -0,0 +1,48 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.ListGroupMembersRequestPacket; +import com.bruis.learnnetty.im.model.ListGroupMembersResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class ListGroupMembersRequestHandler extends SimpleChannelInboundHandler { + + public static final ListGroupMembersRequestHandler INSTANCE = new ListGroupMembersRequestHandler(); + + protected ListGroupMembersRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, ListGroupMembersRequestPacket requestPacket) { + // 1. 获取群的 ChannelGroup + String groupId = requestPacket.getGroupId(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + + // 2. 遍历群成员的 channel,对应的 session,构造群成员的信息 + List sessionList = new ArrayList<>(); + for (Channel channel : channelGroup) { + Session session = SessionUtil.getSession(channel); + sessionList.add(session); + } + + // 3. 构建获取成员列表响应写回到客户端 + ListGroupMembersResponsePacket responsePacket = new ListGroupMembersResponsePacket(); + + responsePacket.setGroupId(groupId); + responsePacket.setSessionList(sessionList); + ctx.channel().writeAndFlush(responsePacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java new file mode 100644 index 0000000..0286a96 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LoginRequestHandler.java @@ -0,0 +1,62 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.LoginRequestPacket; +import com.bruis.learnnetty.im.model.LoginResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.IDUtil; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.*; + +import java.util.Arrays; +import java.util.Date; + +/** + * @Description 接收客户端登录请求 + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class LoginRequestHandler extends SimpleChannelInboundHandler { + + public static final LoginRequestHandler INSTANCE = new LoginRequestHandler(); + + protected LoginRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) { + // 登录校验响应 + LoginResponsePacket loginResponsePacket = new LoginResponsePacket(); + loginResponsePacket.setVersion(loginRequestPacket.getVersion()); + loginResponsePacket.setUserName(loginRequestPacket.getUserName()); + + if (valid(loginRequestPacket)) { + loginResponsePacket.setSuccess(true); + String userId = IDUtil.randomUserId(); + loginResponsePacket.setUserId(userId); + System.out.println("[" + loginRequestPacket.getUserName() + "]登录成功"); + SessionUtil.bindSession(new Session(userId, loginRequestPacket.getUserName()), ctx.channel()); + } else { + loginResponsePacket.setReason("账号密码校验失败"); + loginResponsePacket.setSuccess(false); + System.out.println(new Date() + ": 登录失败!"); + } + + // 登录响应 + ctx.writeAndFlush(loginResponsePacket).addListener((ChannelFutureListener) future -> { + // 关闭channel成功 + Throwable cause = future.cause(); + if (null != cause) { + System.out.println(Arrays.toString(cause.getStackTrace())); + } + }); + } + + private boolean valid(LoginRequestPacket loginRequestPacket) { + return true; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + SessionUtil.unBindSession(ctx.channel()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java new file mode 100644 index 0000000..4436802 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/LogoutRequestHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.LogoutRequestPacket; +import com.bruis.learnnetty.im.model.LogoutResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class LogoutRequestHandler extends SimpleChannelInboundHandler { + + public static final LogoutRequestHandler INSTANCE = new LogoutRequestHandler(); + + protected LogoutRequestHandler () {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, LogoutRequestPacket msg) { + SessionUtil.unBindSession(ctx.channel()); + LogoutResponsePacket logoutResponsePacket = new LogoutResponsePacket(); + logoutResponsePacket.setSuccess(true); + ctx.channel().writeAndFlush(logoutResponsePacket); + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java new file mode 100644 index 0000000..b9b83a0 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/MessageRequestHandler.java @@ -0,0 +1,45 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.MessageRequestPacket; +import com.bruis.learnnetty.im.model.MessageResponsePacket; +import com.bruis.learnnetty.im.session.Session; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +@ChannelHandler.Sharable +public class MessageRequestHandler extends SimpleChannelInboundHandler { + + public static final MessageRequestHandler INSTANCE = new MessageRequestHandler(); + + protected MessageRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext channelHandlerContext, MessageRequestPacket messageRequestPacket) throws Exception { + // 1.拿到消息发送方的会话信息 + Session session = SessionUtil.getSession(channelHandlerContext.channel()); + + // 2.通过消息发送方的会话信息构造要发送的消息 + MessageResponsePacket messageResponsePacket = new MessageResponsePacket(); + messageResponsePacket.setFromUserId(session.getUserId()); + messageResponsePacket.setFromUserName(session.getUserName()); + messageResponsePacket.setMessage(messageRequestPacket.getMessage()); + + // 3.拿到消息接收方的 channel + Channel toUserChannel = SessionUtil.getChannel(messageRequestPacket.getToUserId()); + + // 4.将消息发送给消息接收方 + if (toUserChannel != null && SessionUtil.hasLogin(toUserChannel)) { + toUserChannel.writeAndFlush(messageResponsePacket); + } else { + System.err.println("[" + messageRequestPacket.getToUserId() + "] 不在线,发送失败!"); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java new file mode 100644 index 0000000..86455bb --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/server/handler/QuitGroupRequestHandler.java @@ -0,0 +1,37 @@ +package com.bruis.learnnetty.im.server.handler; + +import com.bruis.learnnetty.im.model.QuitGroupRequestPacket; +import com.bruis.learnnetty.im.model.QuitGroupResponsePacket; +import com.bruis.learnnetty.im.util.SessionUtil; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.group.ChannelGroup; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +@ChannelHandler.Sharable +public class QuitGroupRequestHandler extends SimpleChannelInboundHandler { + + public static final QuitGroupRequestHandler INSTANCE = new QuitGroupRequestHandler(); + + protected QuitGroupRequestHandler() {} + + @Override + protected void channelRead0(ChannelHandlerContext ctx, QuitGroupRequestPacket msg) throws Exception { + String groupId = msg.getGroupId(); + Channel channel = ctx.channel(); + ChannelGroup channelGroup = SessionUtil.getChannelGroup(groupId); + channelGroup.remove(channel); + + QuitGroupResponsePacket responsePacket = new QuitGroupResponsePacket(); + responsePacket.setSuccess(true); + responsePacket.setGroupId(groupId); + + channel.writeAndFlush(responsePacket); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java new file mode 100644 index 0000000..7a7be2d --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/session/Session.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.im.session; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class Session { + + private String userId; + + private String userName; + + public Session(String userId, String userName) { + this.userId = userId; + this.userName = userName; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + @Override + public String toString() { + return userId + "->" + userName; + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java new file mode 100644 index 0000000..3b5403f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/IDUtil.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.im.util; + +import java.util.UUID; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/24 + */ +public class IDUtil { + + public static String randomUserId() { + return UUID.randomUUID().toString().split("-")[0]; + } + +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java new file mode 100644 index 0000000..f4b41c3 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/im/util/SessionUtil.java @@ -0,0 +1,58 @@ +package com.bruis.learnnetty.im.util; + +import com.bruis.learnnetty.im.model.Attributes; +import com.bruis.learnnetty.im.session.Session; +import io.netty.channel.Channel; +import io.netty.channel.group.ChannelGroup; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @Description + * @Author luohaiyang + * @Date 2022/3/23 + */ +public class SessionUtil { + + private static final Map userIdChannelMap = new ConcurrentHashMap<>(); + + private static final Map groupIdChannelGroupMap = new ConcurrentHashMap<>(); + + public static void bindSession(Session session, Channel channel) { + userIdChannelMap.put(session.getUserId(), channel); + channel.attr(Attributes.SESSION).set(session); + } + + public static void unBindSession(Channel channel) { + if (hasLogin(channel)) { + Session session = getSession(channel); + userIdChannelMap.remove(session.getUserId()); + channel.attr(Attributes.SESSION).set(null); + System.out.println(session + " 退出登录"); + } + } + + public static boolean hasLogin(Channel channel) { + + return channel.hasAttr(Attributes.SESSION); + } + + public static Session getSession(Channel channel) { + + return channel.attr(Attributes.SESSION).get(); + } + + public static Channel getChannel(String userId) { + + return userIdChannelMap.get(userId); + } + + public static void bindChannelGroup(String groupId, ChannelGroup channelGroup) { + groupIdChannelGroupMap.put(groupId, channelGroup); + } + + public static ChannelGroup getChannelGroup(String groupId) { + return groupIdChannelGroupMap.get(groupId); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/BusinessException.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/BusinessException.java new file mode 100644 index 0000000..e9e5caf --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/BusinessException.java @@ -0,0 +1,8 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +public class BusinessException extends Exception { + + public BusinessException(String message) { + super(message); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerA.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerA.java new file mode 100644 index 0000000..31004a3 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerA.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author + */ +public class InBoundHandlerA extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerA: " + msg); + ctx.fireChannelRead(msg); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerB.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerB.java new file mode 100644 index 0000000..270d84f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerB.java @@ -0,0 +1,20 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author + */ +public class InBoundHandlerB extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerB: " + msg); + ctx.fireChannelRead(msg); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + ctx.channel().pipeline().fireChannelRead("hello world"); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerC.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerC.java new file mode 100644 index 0000000..e4dcd7e --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/InBoundHandlerC.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.ReferenceCountUtil; + +/** + * @author + */ +public class InBoundHandlerC extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerC: " + msg); + ctx.fireChannelRead(msg); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerA.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerA.java new file mode 100644 index 0000000..4711fd5 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerA.java @@ -0,0 +1,17 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + +/** + * @author + */ +public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerA: " + msg); + ctx.write(msg, promise); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerB.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerB.java new file mode 100644 index 0000000..05a74fd --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerB.java @@ -0,0 +1,27 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + +import java.util.concurrent.TimeUnit; + +/** + * @author + */ +public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter { + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerB: " + msg); + ctx.write(msg, promise); + } + + + @Override + public void handlerAdded(final ChannelHandlerContext ctx) { + ctx.executor().schedule(() -> { + ctx.channel().write("ctx.channel().write -> hello world"); + ctx.write("hello world"); + }, 3, TimeUnit.SECONDS); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerC.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerC.java new file mode 100644 index 0000000..6b49e11 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/OutBoundHandlerC.java @@ -0,0 +1,18 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelOutboundHandlerAdapter; +import io.netty.channel.ChannelPromise; + +/** + * @author + */ +public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerC: " + msg); + ctx.write(msg, promise); +// throw new BusinessException("from OutBoundHandlerC"); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/Server.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/Server.java new file mode 100644 index 0000000..dbcd6f7 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/channelpipeline/Server.java @@ -0,0 +1,48 @@ +package com.bruis.learnnetty.netty.channelpipeline; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.AttributeKey; + +/** + * @author + */ +public final class Server { + + public static void main(String[] args) throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.TCP_NODELAY, true) + .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new OutBoundHandlerA()); + ch.pipeline().addLast(new OutBoundHandlerB()); + ch.pipeline().addLast(new OutBoundHandlerC()); +// ch.pipeline().addLast(new InBoundHandlerA()); +// ch.pipeline().addLast(new InBoundHandlerB()); +// ch.pipeline().addLast(new InBoundHandlerC()); + } + }); + + ChannelFuture f = b.bind(8888).sync(); + + f.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} \ No newline at end of file diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java new file mode 100644 index 0000000..7d69f53 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/ClientHandler.java @@ -0,0 +1,17 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.alibaba.fastjson.JSONObject; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java new file mode 100644 index 0000000..cd3a840 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyClient.java @@ -0,0 +1,91 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.netty.connections.longconnections.ClientHandler; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; + +/** + * @author lhy + * @date 2022/2/16 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + public static ChannelFuture future = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + final ClientHandler clientHandler = new ClientHandler(); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, + 0, 4, 0, 4)); + ch.pipeline().addLast(new StringDecoder()); + ch.pipeline().addLast(clientHandler); + ch.pipeline().addLast(new LengthFieldPrepender(4, false)); + ch.pipeline().addLast(new StringEncoder(Charset.forName("utf-8"))); + } + }); + try { + future = bootstrap.connect("127.0.0.1", 8080).sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 说明:对于这个长连接的例子中,使用了静态化,即单链接、长连接,如果是多链接的话不可使用静态化,需使用线程池。 + * @param msg + * @return + */ + public Object sendRequest(Object msg) { + try { + RequestFuture request = new RequestFuture(); + request.setRequest(msg); + String requestStr = JSONObject.toJSONString(request); + future.channel().writeAndFlush(requestStr); + myselfPrint("我阻塞了", null); + return request.get(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + public static void main(String[] args) { + NettyClient nettyClient = new NettyClient(); + for (int i = 0; i < 10; i++) { + Object result = nettyClient.sendRequest("hello"); + myselfPrint("拿到结果了", result); + } + } + + public static void myselfPrint(String description, Object value) { + StringBuilder builder = new StringBuilder(); + builder.append(Thread.currentThread().getName()); + if (!StringUtils.isEmpty(description)) { + builder.append("-").append(description); + } + if (!StringUtils.isEmpty(value)) { + builder.append("-").append(value); + } + System.out.println(builder.toString()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java new file mode 100644 index 0000000..d668c6b --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/NettyServer.java @@ -0,0 +1,52 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import com.bruis.learnnetty.netty.connections.shortconnections.ServerHandler; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void main(String[] args) { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java new file mode 100644 index 0000000..6c5167a --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/RequestFuture.java @@ -0,0 +1,130 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + public static final AtomicLong aid = new AtomicLong(); + + public RequestFuture() { + id = aid.incrementAndGet(); + addFuture(this); + } + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 表明服务端发送过来的结果已经接收到了,可以signal了 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java new file mode 100644 index 0000000..34ee0d0 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/longconnections/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.netty.connections.longconnections; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java new file mode 100644 index 0000000..6918c68 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ClientHandler.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.concurrent.Promise; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + private Promise promise; + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + promise.setSuccess(response); + } + + public Promise getPromise() { + return promise; + } + + public void setPromise(Promise promise) { + this.promise = promise; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java new file mode 100644 index 0000000..e00bc64 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyClient.java @@ -0,0 +1,66 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.RequestFuture; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import io.netty.util.concurrent.DefaultPromise; +import io.netty.util.concurrent.Promise; + +import java.nio.charset.StandardCharsets; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + } + public static void main(String[] args) { + try { + Promise promise = new DefaultPromise<>(group.next()); + final ClientHandler clientHandler = new ClientHandler(); + clientHandler.setPromise(promise); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(clientHandler) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder(StandardCharsets.UTF_8)); + } + }); + ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync(); + RequestFuture request = new RequestFuture(); + request.setId(1); + request.setRequest("hello world!"); + String requestString = JSONObject.toJSONString(request); + // 向服务端发送请求 + future.channel().writeAndFlush(requestString); + // 同步阻塞等待服务端响应请求 + Response response = promise.get(); + System.out.println(JSONObject.toJSONString(response)); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java new file mode 100644 index 0000000..4453bd6 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/NettyServer.java @@ -0,0 +1,51 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void main(String[] args) { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java new file mode 100644 index 0000000..ec172ec --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/connections/shortconnections/ServerHandler.java @@ -0,0 +1,32 @@ +package com.bruis.learnnetty.netty.connections.shortconnections; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.thread.synchronize.RequestFuture; +import com.bruis.learnnetty.thread.synchronize.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ServerHandler extends ChannelInboundHandlerAdapter { + /** + * 接受客户端发送过来的请求 + * @param ctx + * @param msg + * @throws Exception + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + RequestFuture request = JSONObject.parseObject(msg.toString(), RequestFuture.class); + long id = request.getId(); + Response response = new Response(); + response.setId(id); + response.setResult("我是服务端(" + id + ")"); + /** + * 给客户端发送响应结果 + */ + ctx.channel().writeAndFlush(JSONObject.toJSONString(response)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/FastThreadLocalTest.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/FastThreadLocalTest.java new file mode 100644 index 0000000..ec00911 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/FastThreadLocalTest.java @@ -0,0 +1,62 @@ +package com.bruis.learnnetty.netty.fastthreadlocal; + +import io.netty.util.concurrent.FastThreadLocal; + +/** + * FastThreadLocal测试类 + * + * @author lhy + * @date 2021/7/13 + */ +public class FastThreadLocalTest { + + /** + * FastThreadLocal对象1 + */ + private static FastThreadLocal threadLocal0 = new FastThreadLocal() { + @Override + protected Object initialValue() throws Exception { + Object o = new Object(); + System.out.println("threadLocal0 initialValue: " + o); + return o; + } + + @Override + protected void onRemoval(Object value) throws Exception { + System.out.println("onRemoval"); + } + }; + + private static FastThreadLocal threadLocal1 = new FastThreadLocal() { + @Override + protected Object initialValue() throws Exception { + Object o = new Object(); + System.out.println("threadLocal1 initialValue: " + o); + return o; + } + }; + + public static void main(String[] args) { + new Thread(() -> { + Object object0 = threadLocal0.get(); + System.out.println(Thread.currentThread().getName() + "---> " + object0); + + threadLocal0.set(new Object()); + }) .start(); + + new Thread(() -> { + Object object0 = threadLocal0.get(); + System.out.println(Thread.currentThread().getName() + "---> " + object0); + + System.out.println(Thread.currentThread().getName() + "---> " + (threadLocal0.get() == object0)); +// while (true) { +// System.out.println(Thread.currentThread().getName() + "---> " + (threadLocal0.get() == object0)); +// try { +// Thread.sleep(1000); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// } + }).start(); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/ThreadLocalTest.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/ThreadLocalTest.java new file mode 100644 index 0000000..11b1b33 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/fastthreadlocal/ThreadLocalTest.java @@ -0,0 +1,38 @@ +package com.bruis.learnnetty.netty.fastthreadlocal; + +/** + * @author lhy + * @date 2021/7/14 + */ +public class ThreadLocalTest { + + private static ThreadLocal threadLocal0 = new ThreadLocal<>(); + + private static ThreadLocal threadLocal1 = new ThreadLocal<>(); + + public static void main(String[] args) { + // 线程外 + System.out.println("main线程1: " + threadLocal0.get()); + Object o = new Object(); + threadLocal0.set(o); + + new Thread(() -> { + Object threadObject = threadLocal0.get(); + System.out.println("线程内: " + threadObject); + if (threadObject == null) { + Object newObject = new Object(); + System.out.println("新new一个对象:" + newObject); + threadLocal0.set(newObject); + } + try { + Thread.sleep(1000); + System.out.println("休眠了一秒"); + } catch (Exception e) { + e.printStackTrace(); + } + System.out.println("线程内,从ThreadLocal获取:" + threadLocal0.get()); + }).start(); + + System.out.println("main线程2: " + threadLocal0.get()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ClientTest.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ClientTest.java new file mode 100644 index 0000000..cd1d1d8 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ClientTest.java @@ -0,0 +1,100 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.concurrent.DefaultEventExecutorGroup; + +import java.net.InetSocketAddress; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author lhy + * @date 2021/8/19 + */ +public class ClientTest { + + public static final EventLoopGroup myEventLoopGroup = new NioEventLoopGroup(1, new ThreadFactory() { + + private AtomicInteger threadIndex = new AtomicInteger(0); + + @Override + public Thread newThread(Runnable r) { + return new Thread(r, String.format("MyNettyClientSelector_%d", this.threadIndex.incrementAndGet())); + } + }); + + public static final DefaultEventExecutorGroup nettyHandlerExecutorGroup = new DefaultEventExecutorGroup(1, + new ThreadFactory() { + private AtomicInteger threadIndex = new AtomicInteger(0); + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "nettyHandlerThread_" + this.threadIndex.incrementAndGet()); + } + }); + + public static final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r, "scheduledThread_"); + thread.setDaemon(false); + return thread; + } + }); + + public static void main(String[] args) { + + Bootstrap bootstrap = new Bootstrap() + .group(myEventLoopGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_KEEPALIVE, false) + .option(ChannelOption.SO_SNDBUF, 65535) + .option(ChannelOption.SO_RCVBUF, 65535) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast(nettyHandlerExecutorGroup, + new NettyEncoder(), + new NettyDecoder(), + new ConnectResponseHandler()); + } + }); + + InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9090); + + final ChannelFuture channelFuture = bootstrap.connect(inetSocketAddress); + + if (channelFuture.awaitUninterruptibly(2, TimeUnit.MINUTES)) { +// heartBeat(channelFuture.channel()); + scheduledExecutorService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + heartBeat(channelFuture.channel()); + } + }, 1000, 30 * 1000, TimeUnit.MILLISECONDS); + } + } + + public static void heartBeat(Channel channel) { + String request = "客户端发起了心跳请求"; + RemotingCommand command= new RemotingCommand(); + command.setBody(request.getBytes()); + command.setCode(1); + channel.writeAndFlush(command); + } + + public static class ConnectResponseHandler extends SimpleChannelInboundHandler { + @Override + protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception { + System.out.println("服务端返回消息了:" + new String(msg.getBody())); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyDecoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyDecoder.java new file mode 100644 index 0000000..81be993 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyDecoder.java @@ -0,0 +1,40 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +import java.nio.ByteBuffer; + +/** + * @author lhy + * @date 2021/8/19 + */ +public class NettyDecoder extends LengthFieldBasedFrameDecoder { + + public NettyDecoder() { + super(16777216, 0, 4, 0, 4); + } + + @Override + protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception { + ByteBuf frame = null; + + try { + frame = (ByteBuf) super.decode(ctx, in); + if (null == frame) { + return null; + } + ByteBuffer byteBuffer = frame.nioBuffer(); + return RemotingCommand.decode(byteBuffer); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (null != frame) { + frame.release(); + } + } + + return null; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyEncoder.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyEncoder.java new file mode 100644 index 0000000..1a7fd50 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/NettyEncoder.java @@ -0,0 +1,39 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +import java.nio.ByteBuffer; + +/** + * @author lhy + * @date 2021/8/19 + */ +@ChannelHandler.Sharable +public class NettyEncoder extends MessageToByteEncoder { + + @Override + protected void encode(ChannelHandlerContext ctx, RemotingCommand remotingCommand, ByteBuf out) throws Exception { + try { + ByteBuffer header = remotingCommand.encodeHeader(); + out.writeBytes(header); + byte[] body = remotingCommand.getBody(); + if (null != body) { + out.writeBytes(body); + } +// out.writeBytes(remotingCommand.getBody()); + } catch (Exception e) { + e.printStackTrace(); + ctx.channel().close().addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + // 关闭channel成功 + } + }); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingCommand.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingCommand.java new file mode 100644 index 0000000..6bf9aa9 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingCommand.java @@ -0,0 +1,87 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import java.nio.ByteBuffer; + +/** + * @author lhy + * @date 2021/8/19 + */ +public class RemotingCommand { + + private Integer code; // 请求码 + + private byte[] body; // 请求内容 + + public static RemotingCommand decode(final ByteBuffer byteBuffer) { + int limit = byteBuffer.limit(); + + int oriHeaderLen = byteBuffer.getInt(); + int headerLength = getHeaderLength(oriHeaderLen); + + byte[] headerData = new byte[headerLength]; + byteBuffer.get(headerData); + + int bodyLength = limit - 4 - headerLength; + + byte[] body = new byte[bodyLength]; + byteBuffer.get(body); + RemotingCommand remotingCommand = new RemotingCommand(); + remotingCommand.setBody(body); + return remotingCommand; + } + + public ByteBuffer encodeHeader() { + return encodeHeader(this.body.length); + } + + public ByteBuffer encodeHeader(final int bodyLength) { + int length = 4; + + byte[] headerData; + headerData = this.headerEncode(); + length += headerData.length; // 头 + length += bodyLength; // 请求/响应体 + + ByteBuffer result = ByteBuffer.allocate(4 + length - bodyLength); // 分配header + result.putInt(length); + result.put(markProtocolType(headerData.length, SerializeType.JSON)); + result.put(headerData); // 添加头 + result.flip(); + + return result; + } + + public static byte[] markProtocolType(int source, SerializeType type) { + byte[] result = new byte[4]; + + result[0] = type.getCode(); + result[1] = (byte) ((source >> 16) & 0xFF); + result[2] = (byte) ((source >> 8) & 0xFF); + result[3] = (byte) (source & 0xFF); + return result; + } + + private byte[] headerEncode() { + return RemotingSerializable.encode(this); + } + + public static int getHeaderLength(int length) { + return length & 0xFFFFFF; + } + + public Integer getCode() { + return code; + } + + public void setCode(Integer code) { + this.code = code; + } + + public byte[] getBody() { + return body; + } + + public void setBody(byte[] body) { + this.body = body; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingSerializable.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingSerializable.java new file mode 100644 index 0000000..2dca4f3 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/RemotingSerializable.java @@ -0,0 +1,50 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import com.alibaba.fastjson.JSON; + +import java.nio.charset.Charset; + +/** + * @author lhy + * @date 2021/8/19 + */ +public abstract class RemotingSerializable { + private final static Charset CHARSET_UTF8 = Charset.forName("UTF-8"); + + public static byte[] encode(final Object obj) { + final String json = toJson(obj, false); + if (json != null) { + return json.getBytes(CHARSET_UTF8); + } + return null; + } + + public static String toJson(final Object obj, boolean prettyFormat) { + return JSON.toJSONString(obj, prettyFormat); + } + + public static T decode(final byte[] data, Class classOfT) { + final String json = new String(data, CHARSET_UTF8); + return fromJson(json, classOfT); + } + + public static T fromJson(String json, Class classOfT) { + return JSON.parseObject(json, classOfT); + } + + public byte[] encode() { + final String json = this.toJson(); + if (json != null) { + return json.getBytes(CHARSET_UTF8); + } + return null; + } + + public String toJson() { + return toJson(false); + } + + public String toJson(final boolean prettyFormat) { + return toJson(this, prettyFormat); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/SerializeType.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/SerializeType.java new file mode 100644 index 0000000..6187252 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/SerializeType.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.netty.heartbeat; + +/** + * @author lhy + * @date 2021/8/20 + */ +public enum SerializeType { + JSON((byte) 0); + + private byte code; + + SerializeType(byte code) { + this.code = code; + } + + public static SerializeType valueOf(byte code) { + for (SerializeType serializeType : SerializeType.values()) { + if (serializeType.getCode() == code) { + return serializeType; + } + } + return null; + } + + public byte getCode() { + return code; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ServerTest.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ServerTest.java new file mode 100644 index 0000000..4753a2e --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/heartbeat/ServerTest.java @@ -0,0 +1,55 @@ +package com.bruis.learnnetty.netty.heartbeat; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; + +/** + * @author lhy + * @date 2021/8/19 + */ +public class ServerTest { + public static void main(String[] args) throws InterruptedException { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup=new NioEventLoopGroup(); + ServerBootstrap serverBootstrap=new ServerBootstrap(); + serverBootstrap.group(bossGroup,workerGroup) + .channel(NioServerSocketChannel.class) + //设置线程队列中等待连接的个数 + .option(ChannelOption.SO_BACKLOG,128) + .option(ChannelOption.SO_REUSEADDR, true) + .childOption(ChannelOption.TCP_NODELAY, true) + .childOption(ChannelOption.SO_SNDBUF, 65535) + .childOption(ChannelOption.SO_RCVBUF, 65535) + .childOption(ChannelOption.SO_KEEPALIVE,true) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception { + nioSocketChannel.pipeline() + .addLast(new NettyEncoder(), + new NettyDecoder(), + new ConnectServerHandler()); + + } + }); + + ChannelFuture future = serverBootstrap.bind(9090).sync(); + future.channel().closeFuture().sync(); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + + public static class ConnectServerHandler extends SimpleChannelInboundHandler { + + @Override + protected void channelRead0(ChannelHandlerContext ctx, RemotingCommand msg) throws Exception { + System.out.println("Server服务端:" + new String(msg.getBody())); + RemotingCommand response = new RemotingCommand(); + response.setBody("接受到请求了".getBytes()); + response.setCode(0); + ctx.writeAndFlush(response); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClient.java similarity index 97% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClient.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClient.java index f429e99..db6994b 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClient.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClient.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.netty.demo01; +package com.bruis.learnnetty.netty.quickstart; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClientHandler.java similarity index 98% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClientHandler.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClientHandler.java index f777499..976fdda 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyClientHandler.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyClientHandler.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.netty.demo01; +package com.bruis.learnnetty.netty.quickstart; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyServerHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyServerHandler.java similarity index 96% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyServerHandler.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyServerHandler.java index 9d131fa..b7a9d04 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/NettyServerHandler.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/NettyServerHandler.java @@ -1,11 +1,8 @@ -package com.bruis.learnnetty.netty.demo01; +package com.bruis.learnnetty.netty.quickstart; -import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; -import io.netty.channel.ChannelPipeline; import io.netty.util.CharsetUtil; import java.util.concurrent.TimeUnit; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/Server.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/Server.java similarity index 98% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/Server.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/Server.java index a3f99d2..3adeabc 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/demo01/Server.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/netty/quickstart/Server.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.netty.demo01; +package com.bruis.learnnetty.netty.quickstart; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java similarity index 96% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java index e562ddb..92178e8 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOClient.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOClient.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.nio.demo01; +package com.bruis.learnnetty.nio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java similarity index 98% rename from Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java rename to Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java index c897f1e..15ec3f2 100644 --- a/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/demo01/NIOServer.java +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/nio/NIOServer.java @@ -1,4 +1,4 @@ -package com.bruis.learnnetty.nio.demo01; +package com.bruis.learnnetty.nio; import java.net.InetSocketAddress; import java.nio.ByteBuffer; diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java new file mode 100644 index 0000000..2ceb2a8 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/ClientHandler.java @@ -0,0 +1,19 @@ +package com.bruis.learnnetty.rpc.client; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import com.bruis.learnnetty.rpc.utils.Response; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +public class ClientHandler extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + Response response = JSONObject.parseObject(msg.toString(), Response.class); + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java new file mode 100644 index 0000000..e80d8c1 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/client/NettyClient.java @@ -0,0 +1,92 @@ +package com.bruis.learnnetty.rpc.client; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; +import org.springframework.util.StringUtils; + +import java.nio.charset.Charset; + +/** + * @author lhy + * @date 2022/2/16 + */ +public class NettyClient { + public static EventLoopGroup group = null; + public static Bootstrap bootstrap = null; + public static ChannelFuture future = null; + static { + bootstrap = new Bootstrap(); + group = new NioEventLoopGroup(); + bootstrap.channel(NioSocketChannel.class); + bootstrap.group(group); + bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); + final ClientHandler clientHandler = new ClientHandler(); + bootstrap.handler(new ChannelInitializer() { + @Override + protected void initChannel(NioSocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, + 0, 4, 0, 4)); + ch.pipeline().addLast(new StringDecoder()); + ch.pipeline().addLast(clientHandler); + ch.pipeline().addLast(new LengthFieldPrepender(4, false)); + ch.pipeline().addLast(new StringEncoder(Charset.forName("utf-8"))); + } + }); + try { + future = bootstrap.connect("127.0.0.1", 8080).sync(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 说明:对于这个长连接的例子中,使用了静态化,即单链接、长连接,如果是多链接的话不可使用静态化,需使用线程池。 + * @param msg + * @return + */ + public Object sendRequest(Object msg, String path) { + try { + RequestFuture request = new RequestFuture(); + request.setRequest(msg); + request.setPath(path); + String requestStr = JSONObject.toJSONString(request); + future.channel().writeAndFlush(requestStr); + myselfPrint("我阻塞了", null); + return request.get(); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } + public static void main(String[] args) { + NettyClient nettyClient = new NettyClient(); + for (int i = 0; i < 10; i++) { + Object result = nettyClient.sendRequest("hello-" + i, "getUserNameById"); + myselfPrint("拿到结果了", result); + } + } + + public static void myselfPrint(String description, Object value) { + StringBuilder builder = new StringBuilder(); + builder.append(Thread.currentThread().getName()); + if (!StringUtils.isEmpty(description)) { + builder.append("-").append(description); + } + if (!StringUtils.isEmpty(value)) { + builder.append("-").append(value); + } + System.out.println(builder.toString()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java new file mode 100644 index 0000000..2df7483 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/controller/UserController.java @@ -0,0 +1,17 @@ +package com.bruis.learnnetty.rpc.controller; + +import com.bruis.learnnetty.rpc.utils.Remote; +import org.springframework.stereotype.Controller; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Controller +public class UserController { + @Remote(value = "getUserNameById") + public Object getUserNameById(String userId) { + System.out.println(Thread.currentThread().getName() + "-> 接受到请求:" + userId); + return "做了业务处理了,结果是用户编号userId为" + userId + "的用户姓名为张三"; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java new file mode 100644 index 0000000..b8fbb85 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ApplicationMain.java @@ -0,0 +1,38 @@ +package com.bruis.learnnetty.rpc.server; + +import org.springframework.context.annotation.AnnotationConfigApplicationContext; + +/** + * @author lhy + * @date 2022/2/17 + */ +public class ApplicationMain { + + private static volatile boolean running = true; + + public static void main(String[] args) { + try { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.bruis.learnnetty.rpc"); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + try { + context.stop(); + } catch (Exception e) {} + + synchronized (ApplicationMain.class) { + running = false; + ApplicationMain.class.notify(); + } + })); + context.start(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + System.out.println("服务器已启动"); + synchronized (ApplicationMain.class) { + try { + ApplicationMain.class.wait(); + } catch (Exception e) {} + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java new file mode 100644 index 0000000..6942514 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/InitLoadRemoteMethod.java @@ -0,0 +1,55 @@ +package com.bruis.learnnetty.rpc.server; + +import com.bruis.learnnetty.rpc.utils.Mediator; +import com.bruis.learnnetty.rpc.utils.Remote; +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.core.Ordered; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Controller; + +import java.lang.reflect.Method; +import java.util.Map; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Component +public class InitLoadRemoteMethod implements ApplicationListener, Ordered { + + @Override + public void onApplicationEvent(ContextRefreshedEvent context) { + // 获取Spring容器中带有@Controller的注解类 + Map controllerBeans = context.getApplicationContext() + .getBeansWithAnnotation(Controller.class); + for (String beanName : controllerBeans.keySet()) { + Object beanObj = controllerBeans.get(beanName); + // 获取这个bean的方法集合 + Method[] methods = beanObj.getClass().getMethods(); + for (Method method : methods) { + // 判断这个方法是否带有@Remote注解 + if (method.isAnnotationPresent(Remote.class)) { + Remote remote = method.getAnnotation(Remote.class); + // 注解的值 + String remoteValue = remote.value(); + // 缓存这个类 + Mediator.MethodBean methodBean = new Mediator.MethodBean(); + methodBean.setBean(beanObj); + methodBean.setMethod(method); + // @Remote的value值作为key,MethodBean作为value + Mediator.methodBeans.put(remoteValue, methodBean); + } + } + } + } + + /** + * 值越小优先级越高 + * @return + */ + @Override + public int getOrder() { + return -1; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java new file mode 100644 index 0000000..dd63c71 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyApplicationListener.java @@ -0,0 +1,23 @@ +package com.bruis.learnnetty.rpc.server; + +import org.springframework.context.ApplicationListener; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.stereotype.Component; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Component +public class NettyApplicationListener implements ApplicationListener { + @Override + public void onApplicationEvent(ContextRefreshedEvent event) { + // 开启额外线程启动Netty服务 + new Thread() { + @Override + public void run() { + NettyServer.start(); + } + }.start(); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java new file mode 100644 index 0000000..cc8bfee --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/NettyServer.java @@ -0,0 +1,51 @@ +package com.bruis.learnnetty.rpc.server; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; +import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.codec.string.StringDecoder; +import io.netty.handler.codec.string.StringEncoder; + +/** + * 基于短连接的Netty服务端 + * + * @author lhy + * @date 2022/2/11 + */ +public class NettyServer { + public static void start() { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + try { + ServerBootstrap serverBootstrap = new ServerBootstrap(); + serverBootstrap.group(bossGroup, workerGroup); + serverBootstrap.channel(NioServerSocketChannel.class); + + serverBootstrap.option(ChannelOption.SO_BACKLOG, 128) + .childHandler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4)) + .addLast(new StringDecoder()) + .addLast(new ServerHandler()) + .addLast(new LengthFieldPrepender(4, false)) + .addLast(new StringEncoder()); + } + }); + ChannelFuture future = serverBootstrap.bind(8080).sync(); + future.channel().closeFuture().sync(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java new file mode 100644 index 0000000..7daa7ed --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/server/ServerHandler.java @@ -0,0 +1,29 @@ +package com.bruis.learnnetty.rpc.server; + +import com.alibaba.fastjson.JSONObject; +import com.bruis.learnnetty.rpc.utils.Mediator; +import com.bruis.learnnetty.rpc.utils.RequestFuture; +import com.bruis.learnnetty.rpc.utils.Response; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; + +/** + * @author lhy + * @date 2022/2/11 + */ +@ChannelHandler.Sharable +public class ServerHandler extends ChannelInboundHandlerAdapter { + /** + * 接受客户端发送过来的请求 + * @param ctx + * @param msg + * @throws Exception + */ + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + RequestFuture request = JSONObject.parseObject(msg.toString(), RequestFuture.class); + Response response = Mediator.process(request); + ctx.channel().writeAndFlush(JSONObject.toJSONString(response)); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java new file mode 100644 index 0000000..99ccf31 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Mediator.java @@ -0,0 +1,80 @@ +package com.bruis.learnnetty.rpc.utils; + +import com.alibaba.fastjson.JSONArray; +import com.alibaba.fastjson.JSONObject; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * + * 存储RPC中的映射以及方法Bean + * + * @author lhy + * @date 2022/2/17 + */ +public class Mediator { + + public static Map methodBeans; + + static { + methodBeans = new HashMap<>(); + } + + public static Response process(RequestFuture future) { + Response response = new Response(); + try { + String path = future.getPath(); + MethodBean methodBean = methodBeans.get(path); + if (null != methodBean) { + Object bean = methodBean.getBean(); + Method method = methodBean.getMethod(); + Object request = future.getRequest(); + Class[] parameterTypes = method.getParameterTypes(); + // 此处只支持一个参数,所以写死固定0为索引 + Class parameterType = parameterTypes[0]; + Object param = null; + // 如果参数是List类型 + if (parameterType.isAssignableFrom(List.class)) { + param = JSONArray.parseArray(JSONArray.toJSONString(request), parameterType); + } else if (parameterType.getName().equalsIgnoreCase(String.class.getName())) { + param = request; + } else { + param = JSONObject.parseObject(JSONObject.toJSONString(request), parameterType); + } + // 反射调用方法 + Object result = method.invoke(bean, param); + response.setResult(result); + } + } catch (Exception e) { + e.printStackTrace(); + } + response.setId(future.getId()); + return response; + } + + public static class MethodBean { + + private Object bean; + + private Method method; + + public Object getBean() { + return bean; + } + + public void setBean(Object bean) { + this.bean = bean; + } + + public Method getMethod() { + return method; + } + + public void setMethod(Method method) { + this.method = method; + } + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java new file mode 100644 index 0000000..c173567 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Remote.java @@ -0,0 +1,16 @@ +package com.bruis.learnnetty.rpc.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author lhy + * @date 2022/2/17 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Remote { + String value(); +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java new file mode 100644 index 0000000..340f30a --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/RequestFuture.java @@ -0,0 +1,143 @@ +package com.bruis.learnnetty.rpc.utils; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + /** + * 请求路径 + */ + private String path; + + public static final AtomicLong aid = new AtomicLong(); + + public RequestFuture() { + id = aid.incrementAndGet(); + addFuture(this); + } + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java new file mode 100644 index 0000000..ac5478f --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/rpc/utils/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.rpc.utils; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java new file mode 100644 index 0000000..d0de69c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/FutureMain.java @@ -0,0 +1,64 @@ +package com.bruis.learnnetty.thread.reentranlock; + +import java.util.ArrayList; +import java.util.List; + +/** + * 模拟Netty通讯过程 + * 主线程,获取子线程的结果 + * + * @author lhy + * @date 2022/2/10 + */ +public class FutureMain { + private static List reqs = new ArrayList<>(); + public static void main(String[] args) { + mockClient(); + mockServer(); + } + + /** + * 模拟服务端 接受结果 + */ + private static void mockServer() { + for (RequestFuture req : reqs) { + /** + * 主线程获取结果 + */ + Object result = req.get(); + System.out.println("服务端接受到响应结果:" + result.toString()); + } + } + /** + * 模拟客户端 发送请求 + */ + private static void mockClient() { + for (int i = 0; i < 100; i++) { + long id = i; + RequestFuture req = new RequestFuture(); + req.setId(id); + req.setRequest("hello world"); + /** + * 把请求缓存起来 + */ + RequestFuture.addFuture(req); + /** + * 将请求放入到请求列表中 + */ + reqs.add(req); + sendMsg(req); + SubThread subThread = new SubThread(req); + /** + * 开启子线程 + */ + subThread.start(); + } + } + /** + * 模拟请求处理 + * @param req + */ + private static void sendMsg(RequestFuture req) { + System.out.println("客户端发送数据,请求id为===============" + req.getId()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java new file mode 100644 index 0000000..1fd7dec --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/RequestFuture.java @@ -0,0 +1,123 @@ +package com.bruis.learnnetty.thread.reentranlock; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private final Lock lock = new ReentrantLock(); + private final Condition condition = lock.newCondition(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + lock.lock(); + try { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + condition.await(timeout, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } finally { + lock.unlock(); + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + Objects.requireNonNull(future, "RequestFuture").getLock().lock(); + try { + future.getCondition().signalAll(); + } finally { + Objects.requireNonNull(future, "RequestFuture").getLock().unlock(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public Lock getLock() { + return lock; + } + + public Condition getCondition() { + return condition; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java new file mode 100644 index 0000000..ae1852c --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.thread.reentranlock; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java new file mode 100644 index 0000000..9101e54 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/reentranlock/SubThread.java @@ -0,0 +1,30 @@ +package com.bruis.learnnetty.thread.reentranlock; + +/** + * 子线程,用于模拟服务端处理 + * + * @author lhy + * @date 2022/2/10 + */ +public class SubThread extends Thread { + + private RequestFuture request; + + public SubThread(RequestFuture request) { + this.request = request; + } + + @Override + public void run() { + Response response = new Response(); + response.setId(request.getId()); + response.setResult("服务端响应了结果,线程id: " + Thread.currentThread().getId() + ", 请求id:" + response.getId()); + // 子线程睡眠1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + RequestFuture.received(response); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java new file mode 100644 index 0000000..bd4ee93 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/FutureMain.java @@ -0,0 +1,64 @@ +package com.bruis.learnnetty.thread.synchronize; + +import java.util.ArrayList; +import java.util.List; + +/** + * 模拟Netty通讯过程 + * 主线程,获取子线程的结果 + * + * @author lhy + * @date 2022/2/10 + */ +public class FutureMain { + private static List reqs = new ArrayList<>(); + public static void main(String[] args) { + mockClient(); + mockServer(); + } + + /** + * 模拟服务端 接受结果 + */ + private static void mockServer() { + for (RequestFuture req : reqs) { + /** + * 主线程获取结果 + */ + Object result = req.get(); + System.out.println("服务端接受到响应结果:" + result.toString()); + } + } + /** + * 模拟客户端 发送请求 + */ + private static void mockClient() { + for (int i = 0; i < 100; i++) { + long id = i; + RequestFuture req = new RequestFuture(); + req.setId(id); + req.setRequest("hello world"); + /** + * 把请求缓存起来 + */ + RequestFuture.addFuture(req); + /** + * 将请求放入到请求列表中 + */ + reqs.add(req); + sendMsg(req); + SubThread subThread = new SubThread(req); + /** + * 开启子线程 + */ + subThread.start(); + } + } + /** + * 模拟请求处理 + * @param req + */ + private static void sendMsg(RequestFuture req) { + System.out.println("客户端发送数据,请求id为===============" + req.getId()); + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java new file mode 100644 index 0000000..f1dca80 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/RequestFuture.java @@ -0,0 +1,103 @@ +package com.bruis.learnnetty.thread.synchronize; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 模拟客户端请求类,用于构建请求对象 + * + * @author lhy + * @date 2022/2/10 + */ +public class RequestFuture { + public static Map futures = new ConcurrentHashMap<>(); + private long id; + /** + * 请求参数 + */ + private Object request; + /** + * 响应结果 + */ + private Object result; + /** + * 超时时间 + */ + private long timeout = 5000; + + /** + * 把请求放入本地缓存中 + * @param future + */ + public static void addFuture(RequestFuture future) { + futures.put(future.getId(), future); + } + + /** + * 同步获取响应结果 + * @return + */ + public Object get() { + synchronized (this) { + while (this.result == null) { + try { + // 主线程默认等待5s,然后查看下结果 + this.wait(timeout); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + return this.result; + } + + /** + * 异步线程将结果返回主线程 + * @param result + */ + public static void received(Response result) { + RequestFuture future = futures.remove(result.getId()); + if (null != future) { + future.setResult(result.getResult()); + } + /** + * 通知主线程 + */ + synchronized (Objects.requireNonNull(future, "RequestFuture")) { + future.notify(); + } + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getRequest() { + return request; + } + + public void setRequest(Object request) { + this.request = request; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java new file mode 100644 index 0000000..c5dfc05 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/Response.java @@ -0,0 +1,28 @@ +package com.bruis.learnnetty.thread.synchronize; + +/** + * 响应结果类 + * + * @author lhy + * @date 2022/2/10 + */ +public class Response { + private long id; + private Object result; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java new file mode 100644 index 0000000..16e2a57 --- /dev/null +++ b/Spring-Netty/src/main/java/com/bruis/learnnetty/thread/synchronize/SubThread.java @@ -0,0 +1,31 @@ +package com.bruis.learnnetty.thread.synchronize; + +/** + * 子线程,用于模拟服务端处理 + * + * @author lhy + * @date 2022/2/10 + */ +public class SubThread extends Thread { + + private RequestFuture request; + + public SubThread(RequestFuture request) { + this.request = request; + } + + @Override + public void run() { + Response response = new Response(); + response.setId(request.getId()); + response.setResult("服务端响应了结果,线程id: " + Thread.currentThread().getId() + ", 请求id:" + response.getId()); + // 子线程睡眠1s + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println(this + " -> 当前线程准备调用received: " + Thread.currentThread().getName()); + RequestFuture.received(response); + } +} diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\345\274\225\347\224\250.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\345\274\225\347\224\250.md" new file mode 100644 index 0000000..089722d --- /dev/null +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\345\274\225\347\224\250.md" @@ -0,0 +1,23 @@ +## 前言 + +## 正文 + +就DubboProtocolTest类下的testDubboProtocol()方法,服务引用流程: + +1. ProtocolFilterWrapper#refer() +2. ProtocolListenerWrapper#refer() +3. AbstractProtocol#refer() +4. DubboProtocol#protocolBindingRefer() +5. DubboProtocol#getClients() +6. DubboProtocol#getSharedClient() +7. DubboProtocol#buildReferenceCountExchangeClientList() +8. DubboProtocol#buildReferenceCountExchangeClient() +9. DubboProtocol#initClient() +10. Exchangers#connect() +11. HeaderExchanger#connect() +12. Transporters#connect() +13. Transporter$Adaptive,代理出来的类,在服务暴露中有代码 +14. NettyTransporter#connect() +15. NettyClient#init() +16. AbstractClient#init() +17. NettyClient#doOpen() \ No newline at end of file diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\346\232\264\351\234\262.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\346\232\264\351\234\262.md" new file mode 100644 index 0000000..eede346 --- /dev/null +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\342\200\224\342\200\224\346\234\215\345\212\241\346\232\264\351\234\262.md" @@ -0,0 +1,144 @@ +```Java +package org.apache.dubbo.rpc; +import org.apache.dubbo.common.extension.ExtensionLoader; +public class Protocol$Adaptive implements org.apache.dubbo.rpc.Protocol { + public void destroy() { + throw new UnsupportedOperationException("The method public abstract void org.apache.dubbo.rpc.Protocol.destroy() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); + } + public int getDefaultPort() { + throw new UnsupportedOperationException("The method public abstract int org.apache.dubbo.rpc.Protocol.getDefaultPort() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); + } + public org.apache.dubbo.rpc.Invoker refer(java.lang.Class arg0, org.apache.dubbo.common.URL arg1) throws org.apache.dubbo.rpc.RpcException { + if (arg1 == null) throw new IllegalArgumentException("url == null"); + org.apache.dubbo.common.URL url = arg1; + String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); + // 这里extension为ProtocolFilterWrapper类 + org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); + return extension.refer(arg0, arg1); + } + public org.apache.dubbo.rpc.Exporter export(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { + if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); + if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); + org.apache.dubbo.common.URL url = arg0.getUrl(); + String extName = ( url.getProtocol() == null ? "dubbo" : url.getProtocol() ); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.Protocol) name from url (" + url.toString() + ") use keys([protocol])"); + // 此处传入的extName为dubbo,extension获得的扩展点实现类为ProtocolFilterWrapper + org.apache.dubbo.rpc.Protocol extension = (org.apache.dubbo.rpc.Protocol)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.Protocol.class).getExtension(extName); + return extension.export(arg0); + } + public java.util.List getServers() { + throw new UnsupportedOperationException("The method public default java.util.List org.apache.dubbo.rpc.Protocol.getServers() of interface org.apache.dubbo.rpc.Protocol is not adaptive method!"); + } +} +``` + +```Java +package org.apache.dubbo.rpc; +import org.apache.dubbo.common.extension.ExtensionLoader; +public class ProxyFactory$Adaptive implements org.apache.dubbo.rpc.ProxyFactory { + public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0) throws org.apache.dubbo.rpc.RpcException { + if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); + if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); + org.apache.dubbo.common.URL url = arg0.getUrl(); + // 判断如果取不到proxy,则使用默认值javassist + String extName = url.getParameter("proxy", "javassist"); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); + // 这里extension是StubProxyFactoryWrapper对象 + org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); + return extension.getProxy(arg0); + } + public java.lang.Object getProxy(org.apache.dubbo.rpc.Invoker arg0, Boolean arg1) throws org.apache.dubbo.rpc.RpcException { + if (arg0 == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument == null"); + if (arg0.getUrl() == null) throw new IllegalArgumentException("org.apache.dubbo.rpc.Invoker argument getUrl() == null"); + org.apache.dubbo.common.URL url = arg0.getUrl(); + String extName = url.getParameter("proxy", "javassist"); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); + org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); + return extension.getProxy(arg0, arg1); + } + public org.apache.dubbo.rpc.Invoker getInvoker(java.lang.Object arg0, java.lang.Class arg1, org.apache.dubbo.common.URL arg2) throws org.apache.dubbo.rpc.RpcException { + if (arg2 == null) throw new IllegalArgumentException("url == null"); + org.apache.dubbo.common.URL url = arg2; + String extName = url.getParameter("proxy", "javassist"); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.rpc.ProxyFactory) name from url (" + url.toString() + ") use keys([proxy])"); + // 这里获取到的ProxyFactory的扩展点是StubProxyFactoryWrapper,StubProxyFactoryWrapper是ProxyFactory的实现类,由于ProxyFactory注解@SPI("javassist"),所以extName取javassist值 + org.apache.dubbo.rpc.ProxyFactory extension = (org.apache.dubbo.rpc.ProxyFactory)ExtensionLoader.getExtensionLoader(org.apache.dubbo.rpc.ProxyFactory.class).getExtension(extName); + // 所以去调用了StubProxyFactoryWrapper#getInvoker()方法,在该方法中调用的是JavassistProxyFactory的getInvoker方法,最终实际 + // 返回的是一个JavassistProxyFactory的一个匿名内部类:AbstractProxyInvoker + return extension.getInvoker(arg0, arg1, arg2); + } +} +``` + +```Java +package org.apache.dubbo.remoting; +import org.apache.dubbo.common.extension.ExtensionLoader; +public class Transporter$Adaptive implements org.apache.dubbo.remoting.Transporter { + public org.apache.dubbo.remoting.Client connect(org.apache.dubbo.common.URL arg0, org.apache.dubbo.remoting.ChannelHandler arg1) throws org.apache.dubbo.remoting.RemotingException { + if (arg0 == null) throw new IllegalArgumentException("url == null"); + org.apache.dubbo.common.URL url = arg0; + String extName = url.getParameter("client", url.getParameter("transporter", "netty")); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([client, transporter])"); + org.apache.dubbo.remoting.Transporter extension = (org.apache.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class).getExtension(extName); + return extension.connect(arg0, arg1); + } + public org.apache.dubbo.remoting.RemotingServer bind(org.apache.dubbo.common.URL arg0, org.apache.dubbo.remoting.ChannelHandler arg1) throws org.apache.dubbo.remoting.RemotingException { + if (arg0 == null) throw new IllegalArgumentException("url == null"); + org.apache.dubbo.common.URL url = arg0; + String extName = url.getParameter("server", url.getParameter("transporter", "netty")); + if(extName == null) throw new IllegalStateException("Failed to get extension (org.apache.dubbo.remoting.Transporter) name from url (" + url.toString() + ") use keys([server, transporter])"); + // 这里获取的扩展点实现类是netty4包下的NettyTransporter + org.apache.dubbo.remoting.Transporter extension = (org.apache.dubbo.remoting.Transporter)ExtensionLoader.getExtensionLoader(org.apache.dubbo.remoting.Transporter.class).getExtension(extName); + return extension.bind(arg0, arg1); + } +} +``` + +对于JavassistProxyFactory对象中Invoker类型的匿名内部类,类结构如下图所示 +![export01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/export01.png) + +JavassistProxyFactory代码如下: +```Java +public class JavassistProxyFactory extends AbstractProxyFactory { + + @Override + @SuppressWarnings("unchecked") + public T getProxy(Invoker invoker, Class[] interfaces) { + return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker)); + } + + @Override + public Invoker getInvoker(T proxy, Class type, URL url) { + // TODO Wrapper cannot handle this scenario correctly: the classname contains '$' + final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type); + return new AbstractProxyInvoker(proxy, type, url) { + @Override + protected Object doInvoke(T proxy, String methodName, + Class[] parameterTypes, + Object[] arguments) throws Throwable { + return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments); + } + }; + } + +} +``` + + +- testDubboProtocol()调用流程 + +1. 首先protocol和proxy变量分别在DubboProtocolTest.java类加载过程时就已经分别加载好了,这里的Protocol和ProxyFactory对象都生成的是代理对象,分别为Protocol$Adaptive和ProxyFactory$Adaptive对象,动态代理处的代码在上面展示了; +2. 首先调用proxy#getInvoker(),调用的实际是ProxyFactory$Adaptive代理对象的getInvoker()方法,在上图可以看到具体逻辑,其方法逻辑中需要去获取ProxyFactory对象的扩展点实现类,经过调试ProxyFactory对象的扩展点实现类为StubProxyFactoryWrapper,最终会调用到JavassistProxyFactory的getInvoker()方法,它会最终生成一个AbstractProxyInvoker()匿名类对象,所以调试的时候可以发现invoker的引用是JavassistProxyFactory$1; +3. 获取到invoker之后,就传入到Protocol的扩展点实现类中去调用export()方法,由上图的Protocol$Adaptive可以知道,在export()方法中会去生成Protocol的扩展点实现类,这里的实现类是ProtocolFilterWrapper(这里为什么是这个实现类呢?去调研一下)。经过代理类的传递包装,最终来到了ProtocolFilterWrapper#export()方法; +4. 接着就是调用ProtocolFilterWrapper#buildInvocation()方法,构建调用链。就是先去获取Filter的扩展点实现类集合对象,然后倒叙遍历该集合对象然后将invoker对象和最后一个Filter对象封装为另一个Invoker,然后再继续传递到和上一个Filter对象继续封装成Invoker,以此往复封装。最终的结果就是将传入的invoker对象封装到了最后一个Filter之后,请求进来之后需要调用所有的Filter过后才会调用invoker对象,形成了一个Invoker链; + +效果如下: +![export02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/export02.png) + +还需要注意的是,在ProtocolFilterWrapper#buildInvokerChain()方法中,生成的是匿名对象,即将3中传递过来的JavassistProxyFactory$1又包装起来放在了ProtocolFilterWrapper对象中,所以你会发现此时的invoker对象是ProtocolFilterWrapper$1; + +5. 在ProtocolFilterWrapper#export()中,会调用ProtocolListenerWrapper#export()对象,在ProtocolListenerWrapper#export()中会返回一个ListenerExporterWrapper的匿名内部类对象,生成该匿名内部类之前会调用DubboProtocol#export()方法。 +6. 千辛万苦,终于来到了DubboProtocol#export()的openServer()方法了,在openServer()方法中,会调用createServer(url)方法去创建Server;在DubboProtocol中会调用Exchangers#bind()去和Netty服务端进行绑定? +7. 随后调用Exchangers#getExchanger()方法去获取Exchanger的扩展点实现类,他的扩展点实现类是HeaderExchanger,即调用的是HeaderExchagner#bind()方法。在其方法中会构造一个HeaderExchangeServer对象,但是在传入入参时需要调用Transporters#bind()方法,将返回的对象作为入参。 +8. 随后调用Transporters#getTransporter()方法,去获取Transporter的自适应扩展点实现类,这里实际获取的是一个代理类,是Transporter$Adaptive,类实现体在上面。接着就是调用的Transporter$Adaptive的bind()方法。最终去调用的netty4包下的NettyTransporter#bind() \ No newline at end of file diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204URL.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204URL.md" index cfeef64..99cea39 100644 --- "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204URL.md" +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204URL.md" @@ -34,7 +34,7 @@ dubbo://172.17.32.91:20880/org.apache.dubbo.demo.DemoService?anyhost=true&applic 先看下Dubbo中org.apache.dubbo.common包下的URL类源码: -``` +```Java public /*final**/ class URL implements Serializable { @@ -154,5 +154,3 @@ ServiceConfig这个类非常复杂,后面专门拿一篇来讲解。 3. Dubbo中的服务引用 -## 参考 - diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266.md" deleted file mode 100644 index 7d702e8..0000000 --- "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266.md" +++ /dev/null @@ -1,64 +0,0 @@ -## 前言 - -Dubbo的SPI机制是什么呢?首先,SPI全称为(Service Provider Interface),主要是被框架开发人员使用的一种技术。Dubbo的SPI机制提供的是一种将服务Provider接口定义在META-INF中,当Dubbo服务启动时, -通过SPI加载机制加载文件中的接口,从而使用那些被加载的接口。那么这么做的目的是什么呢?这么做的目的就是为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则)。这种"对扩展开放,对修改封闭"的原则能 -在维持内核功能稳定的基础上,更好的对系统功能进行扩展。换句话说,基于Dubbo SPI加载机制,让整个框架的接口和具体实现完全解耦,从而奠定了整个框架良好可扩展性的基础。 - -Dubbo SPI是参考了JDK原生的SPI机制,进行了性能优化以及功能增强。 - -## 正文 - -### 1. Java SPI - -Javs SPI使用的是策略模式,一个接口多种实现。我们只负责声明接口,具体的实现并不在程序中直接确定,而是由程序之外的配置掌控,用于具体实现的装配。 - -Java SPI的定义及使用步骤如下: -1. 定义一个接口以及对应的方法 -2. 编写该接口的一个实现类 -3. 在META-INF/services/目录下,创建一个以接口全路径命名的文件,如com.test.spi.PrintService -4. 文件内容为具体实现类的全路径名,如果有多个,则用分行符分隔 -5. 在代码中通过java.util.ServiceLoader来加载具体的实现类 - -在com.test.spi包目录下,定义了一个PrintService接口和一个PrintServiceImpl实现类,然后在resources目录下定义了一个META-INF/services/com.test.spi.PrintService,注意这里定义的是一个 -全路径名称的文件。 - -``` -public interface Printservice ( - void printlnfo(); -} -``` - -``` -public class PrintServicelmpl implements Printservice { - @Override - public void printlnfo() { - System.out.println("hello world"); - } -} -``` - -``` -public static void main(String[] args) ( - ServiceLoader serviceServiceLoader = - ServiceLoader.load(PrintService.class); - for (Printservice printservice : serviceServiceLoader) ( - //此处会输出:hello world 获取所有的SPI实现,循环调用 - printService.printInfo(); printlnfo()方法,会打印出 hello world - } -} -``` - -在JDK SPI中,是通过ServiceLoader来获取所有接口实现的。 - -### 2. Dubbo SPI - -Dubbo SPI没有直接使用Java SPL而是在它的思想上又做了一定的改进,形成了一套自己的配置规范和特性。同时,Dubbo SPI又兼容Java SPL服务在启动的时候,Dubbo就会查找这些扩展点的所有实现 - -Dubbo SPI之于JDK SPI,做到了三点优化: -1. 不同于JDK SPI会一次性实例化扩展点所有实现,因为JDK SPI有扩展实现,则初始化会很耗时,并且如果没有用上也要加载,则会很浪费资源。而Dubbo SPI只会加载扩展点,而不会对其进行初始化,并且Dubbo SPI中 -会根据不同的实现类来缓存到内存中,性能上得到了很大的优化。 -2. JDK SPI如果对扩展加载失败,则连扩展的名称都获取不到,并且失败原因会被吃掉,而Dubbo SPI则会将异常堆栈保留下来,方便后序 -对其异常信息进行分析。 -3. Dubbo SPI增加了对IOC和AOP的支持,在Dubbo中,一个扩展点可以通过setter来注入到其他扩展点中。 - -未完待续.... \ No newline at end of file diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\212\357\274\211.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\212\357\274\211.md" new file mode 100644 index 0000000..b0d01fa --- /dev/null +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\212\357\274\211.md" @@ -0,0 +1,225 @@ +## 前言 + +Dubbo的SPI机制是什么呢?首先,SPI全称为(Service Provider Interface),主要是被框架开发人员使用的一种技术。 + +Dubbo的SPI机制提供的是一种将服务Provider接口定义在META-INF中,当Dubbo服务启动时, +通过SPI加载机制加载文件中的接口,从而使用那些被加载的接口。那么这么做的目的是什么呢?这么做的目的就是为了更好地达到 OCP 原则(即“对扩展开放,对修改封闭”的原则)。这种"对扩展开放,对修改封闭"的原则能在维持内核功能稳定的基础上,更好的对系统功能进行扩展。换句话说,基于Dubbo SPI加载机制,让整个框架的接口和具体实现完全解耦,从而奠定了整个框架良好可扩展性的基础。 + + +SPI中两个核心就是:扩展点和扩展点实现类。 + +在JDK SPI和Dubbo SPI中,都是通过在配置文件中定义KV键值对来定义对应的扩展点和扩展点实现类,其中Key表示的是扩展点名称,Value表示的是扩展点实现类的全路径名称。 + +Dubbo SPI是参考了JDK原生的SPI机制,进行了性能优化以及功能增强。 + +## 正文 + +### 1. Java SPI + +Javs SPI使用的是策略模式,一个接口多种实现。我们只负责声明接口,具体的实现并不在程序中直接确定,而是由程序之外的配置掌控,用于具体实现的装配。 + +Java SPI的定义及使用步骤如下: +1. 定义一个接口以及对应的方法 +2. 编写该接口的一个实现类 +3. 在META-INF/services/目录下,创建一个以接口全路径命名的文件,如com.test.spi.PrintService +4. 文件内容为具体实现类的全路径名,如果有多个,则用分行符分隔 +5. 在代码中通过java.util.ServiceLoader来加载具体的实现类 + +在com.test.spi包目录下,定义了一个PrintService接口和一个PrintServiceImpl实现类,然后在resources目录下定义了一个META-INF/services/com.test.spi.PrintService,注意这里定义的是一个 +全路径名称的文件。 + +```Java +public interface Printservice ( + void printlnfo(); +} +``` + +```Java +public class PrintServicelmpl implements Printservice { + @Override + public void printlnfo() { + System.out.println("hello world"); + } +} +``` + +```Java +public static void main(String[] args) ( + ServiceLoader serviceServiceLoader = + ServiceLoader.load(PrintService.class); + for (Printservice printservice : serviceServiceLoader) ( + //此处会输出:hello world 获取所有的SPI实现,循环调用 + printService.printInfo(); printlnfo()方法,会打印出 hello world + } +} +``` + +在JDK SPI中,是通过ServiceLoader来获取所有接口实现的。 + +最常见的JDK SPI就是com.mysql.cj.jdbc.Driver 接口,它的实现类是有用户通过配置文件来设定的,Driver接口就是一个扩展点。 + + +![jdk-spi](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/jdk-spi.png) + +![jdk-spi-driver](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/jdk-spi-driver.png) + +### 2. Dubbo SPI + +Dubbo SPI没有直接使用Java SPI而是在它的思想上又做了一定的改进,形成了一套自己的配置规范和特性。同时,Dubbo SPI又兼容Java SPI服务在启动的时候,Dubbo就会查找这些扩展点的所有实现。 + +Dubbo SPI之于JDK SPI,做到了三点优化: +1. 不同于JDK SPI会一次性实例化扩展点所有实现,因为JDK SPI有扩展实现,则初始化会很耗时,并且如果没有用上也要加载,则会很浪费资源。而Dubbo SPI只会加载扩展点,而不会对其进行初始化,并且Dubbo SPI中 +会根据不同的实现类来缓存到内存中,性能上得到了很大的优化。 +2. JDK SPI如果对扩展加载失败,则连扩展的名称都获取不到,并且失败原因会被吃掉,而Dubbo SPI则会将异常堆栈保留下来,方便后序对其异常信息进行分析。 +3. Dubbo SPI增加了对IOC和AOP的支持,在Dubbo中,一个扩展点可以通过setter来注入到其他扩展点中。 + + +这里再次统一一下SPI的核心概念: + +- 扩展点 +- 扩展点的实现类 + +Dubbo SPI的核心就是围绕着怎么获取扩展点以及扩展点实现类来进行的。那么现在需要先知道,扩展点以及扩展点实现类存放位置? + +#### 2.1 Dubbo SPI配置文件及其存放位置 + +在Dubbo SPI中,按照用途将SPI配置文件分为三类以META-INF开头的目录(META-INF开头目录通常都存放在类路径下): + +- META-INF/services/:该目录的SPI配置文件是用来兼容JDK SPI的 +- META-INF/dubbo/:用来存放用户自定义的SPI配置文件 +- META-INF/dubbo/internal:该目录用于存放Dubbo内部使用的SPI配置文件 + +在SPI配置文件中,都是以KV格式存在的配置内容,例如Dubbo源码中的SPI配置文件内容: +``` +dubbo=org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol +``` + +key表示的是扩展点名称,而value表示的是扩展点的实现类的全限定类名。另外,SPI配置文件名称就是扩展点实现类的全限定类名。 +![Dubbo-SPI-01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/dubbo-spi-01.png) + +那么,扩展点及其实现类以及存放在SPI文件中了,那么Dubbo应用程序该如何将其加载进行JVM内存中呢? + +#### 2.2 Dubbo的ExtensionLoader + +ExtensionLoader即扩展点加载器,它是Dubbo SPI的核心,负责加载扩展点即扩展点实现类,先看下其内部重要的几个成员变量: + + +![Dubbo-SPI-02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/dubbo-spi-02.png) + +![Dubbo-SPI-03](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/dubbo-spi-03.png) + +这里的扩展点加载策略有三种: +- DubboInternalLoadingStrategy(加载内部的SPI) +- DubboLoadingStrategy(加载用户自定义的SPI) +- ServiceLoadingStrategy(加载用于兼容JDK的SPI) + +并且其内部默认优先级为:DubboInternalLoadingStrategy > DubboLoadingStrategy > ServiceLoadingStrategy + +![Dubbo-SPI-04](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/dubbo-spi-04.png) + +上图清楚的展示了LoadingStrategy接口及其实现类的关系。LoadingStrategy继承了Prioritized,因而其实现类会有优先级之分,而Dubbo默认是使用的DubboInternalLoadingStrategy,查看其三个类的源码: + +```Java +public class DubboInternalLoadingStrategy implements LoadingStrategy { + + // 表示要加载的目录位置 + @Override + public String directory() { + return "META-INF/dubbo/internal/"; + } + + // 获取优先值,用于进行Comparable接口的compareTo优先级比较 + @Override + public int getPriority() { + return MAX_PRIORITY; + } +} +``` + +```Java +public class DubboLoadingStrategy implements LoadingStrategy { + + // 表示要加载的目录位置 + @Override + public String directory() { + return "META-INF/dubbo/"; + } + + @Override + public boolean overridden() { + return true; + } + + // 获取优先值,用于进行Comparable接口的compareTo优先级比较 + @Override + public int getPriority() { + return NORMAL_PRIORITY; + } + + +} +``` + +```Java +public class ServicesLoadingStrategy implements LoadingStrategy { + + // 表示要加载的目录位置 + @Override + public String directory() { + return "META-INF/services/"; + } + + @Override + public boolean overridden() { + return true; + } + + // 获取优先值,用于进行Comparable接口的compareTo优先级比较 + @Override + public int getPriority() { + return MIN_PRIORITY; + } + +} +``` + +这里的MAX_PRIORITY、NORMAL_PRIORITY和MIN_PRIORITY时定义在Prioritized这个接口中的,查看一下Prioritized中定义的值以及实现的compareTo方法: + +```Java + /** + * The maximum priority + */ + int MAX_PRIORITY = Integer.MIN_VALUE; + + /** + * The minimum priority + */ + int MIN_PRIORITY = Integer.MAX_VALUE; + + /** + * Normal Priority + */ + int NORMAL_PRIORITY = 0; + + /** + * Get the priority + * + * @return the default is {@link #MIN_PRIORITY minimum one} + */ + default int getPriority() { + return NORMAL_PRIORITY; + } + + @Override + default int compareTo(Prioritized that) { + return compare(this.getPriority(), that.getPriority()); + } +``` + +所以在Dubbo中,默认的优先级为:DubboInternalLoadingStrategy > DubboLoadingStrategy > ServiceLoadingStrategy + +即优先加载:META-INF/dubbo/internal目录下的SPI配置文件。 + +要用好Dubbo源码中的Test目录中各种场景的test,在通读完整篇文章后再调用Test代码,然后debug一步一步调试,能够加深源码底层调用逻辑的理解。SPI机制的中篇以及下篇会通过大量的Test测试用例来进行底层逻辑分析讲解。 + +> 由于篇幅过长,关于Dubbo SPI机制的原理分析将分为上、中、下三篇 diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" new file mode 100644 index 0000000..8bd2172 --- /dev/null +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\213\357\274\211.md" @@ -0,0 +1,391 @@ +## 前言 + +在Dubbo的SPI机制上、中两篇文章中,已经讲解了SPI的核心机制,本篇文章讲解SPI中的几个核心注解。 + +## 正文 + +再Dubbo的SPI中,核心注解包括了:@SPI、@Adaptive、@Activate。 + +### 1. @SPI注解 + +在Dubbo中某个接口被@SPI注解修饰时,就表示该接口是扩展接口,在前文中提到的SimpleExt就是由@SPI注解修饰,因而SimpleExt这个接口表示的就是一个扩展点接口。 + +另外在@SPI注解的value值指定了扩展点默认的实现类名,例如SimpleExt注解由@SPI("impl1")修饰,则表示它的实现类名为:SimpleExtImpl1,查看SPI的配置文件可证: + +```Java +# Comment 1 +impl1=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1#Hello World +impl2=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl2 # Comment 2 + impl3=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl3 # with head space +``` + +Dubbo通过ExtensionLoader去加载上述SPI配置文件,然后读取到@SPI("impl1")接口的扩展点实现类为SimpleExtImpl1,随后通过getExtension()方法获取扩展点实现类的对象,那么Dubbo是如何处理@SPI注解的呢? + +Dubbo SPI的核心逻辑几乎都封装在ExtensionLoader之中,ExtensionLoader存放于dubbo-common模块的extension保重,功能类似于JDK SPI中的java.util.ServiceLoader。 + +下面展示了ExtensionLoader最常用的使用方式: +```Java +SimpleExt ext = ExtensionLoader.getExtensionLoader(SimpleExt.class).getDefaultExtension(); +``` + +首先时调用ExtensionLoader#getExtensionLoader(SimpleExt.class),来获取SimpleExt类型的ExtensionLoader。查看ExtensionLoader源码如下: + +```Java + public static ExtensionLoader getExtensionLoader(Class type) { + if (type == null) { + throw new IllegalArgumentException("Extension type == null"); + } + if (!type.isInterface()) { + throw new IllegalArgumentException("Extension type (" + type + ") is not an interface!"); + } + if (!withExtensionAnnotation(type)) { + throw new IllegalArgumentException("Extension type (" + type + + ") is not an extension, because it is NOT annotated with @" + SPI.class.getSimpleName() + "!"); + } + + ExtensionLoader loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); + if (loader == null) { + // 如果初始指定的EXTENSION_LOADER为空值,则新new一个ExtensionLoader对象存放至其中。要注意ExtensionLoader的构造方法内容! + EXTENSION_LOADERS.putIfAbsent(type, new ExtensionLoader(type)); + loader = (ExtensionLoader) EXTENSION_LOADERS.get(type); + } + return loader; + } +``` + +getExtensionLoader方法首先会去判断EXTENSION_LOADERS缓存中是否已经缓存了该类型的扩展点加载器,如果没有则new一个该类型的ExtensionLoader并添加进EXTENSION_LOADERS中。但需要注意的是ExtensionLoader的构造方法 +中,是会先创建默认的ExtensionFactory类型的ExtensionLoader对象,然后调用getAdaptiveExtension()方法创建适配类型的扩展点实现类。 + +```Java + private ExtensionLoader(Class type) { + this.type = type; + // 从此处可以知道,对于默认的ExtensionFactory.class来说,是没有objectFactory熟悉对象值的 + // 如果type不为ExtensionFactory类型的,则会创建一个ExtensionFactory的适配工厂来成为objectFactory对象属性 + objectFactory = (type == ExtensionFactory.class ? null : ExtensionLoader.getExtensionLoader(ExtensionFactory.class).getAdaptiveExtension()); + } +``` + +从dubbo-common模块下的org.apache.dubbo.common.extension.ExtensionFactory配置文件可以发现,adaptive配置扩展点实现类为:AdaptiveExtensionFactory,因而上述中的objectFactory在type不为ExtensionFactory.class类型时, +被赋值为AdaptiveExtensionFactory。 + +下面看下getExtensionClass()方法的逻辑 +```Java + private Class getExtensionClass(String name) { + if (type == null) { + throw new IllegalArgumentException("Extension type == null"); + } + if (name == null) { + throw new IllegalArgumentException("Extension name == null"); + } + // 从获取到的Map集合中取出key为name类型的扩展点实现类 + return getExtensionClasses().get(name); + } +``` + +```Java + private Map> getExtensionClasses() { + Map> classes = cachedClasses.get(); + // 双重检测,防止并发环境下指令重排序,cachedClasses是static类型 + if (classes == null) { + synchronized (cachedClasses) { + classes = cachedClasses.get(); + if (classes == null) { + // 加载扩展点实现类 + classes = loadExtensionClasses(); + cachedClasses.set(classes); + } + } + } + return classes; + } +``` + +```Java + private Map> loadExtensionClasses() { + // 缓存默认的扩展点名称,这里会去读取@SPI注解 + cacheDefaultExtensionName(); + + Map> extensionClasses = new HashMap<>(); + + for (LoadingStrategy strategy : strategies) { + // 加载SPI配置文件中的扩展点实现类 + loadDirectory(extensionClasses, strategy.directory(), type.getName(), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); + loadDirectory(extensionClasses, strategy.directory(), type.getName().replace("org.apache", "com.alibaba"), strategy.preferExtensionClassLoader(), strategy.overridden(), strategy.excludedPackages()); + } + + // 这里只会返回非Adaptive和非Wrapper类型的扩展点实现类Class,因为Adaptive会被缓存到cachedAdaptiveClasses缓存中,而Wrapper类型的类会被缓存到cachedWrapperClasses缓存中。 + return extensionClasses; + } + + private void cacheDefaultExtensionName() { + // 获取 SPI的注解对象 + final SPI defaultAnnotation = type.getAnnotation(SPI.class); + if (defaultAnnotation == null) { + return; + } + + // 获取@SPI注解的value值 + String value = defaultAnnotation.value(); + if ((value = value.trim()).length() > 0) { + String[] names = NAME_SEPARATOR.split(value); + // 如果names长度大于1,则表示有两个扩展点名称,直接抛出异常 + if (names.length > 1) { + throw new IllegalStateException("More than 1 default extension name on extension " + type.getName() + + ": " + Arrays.toString(names)); + } + if (names.length == 1) { + // 将@SPI的value值缓存到cachedDefaultName + cachedDefaultName = names[0]; + } + } + } +``` + +```Java + // 加载SPI配置文件目录 + private void loadDirectory(Map> extensionClasses, String dir, String type, + boolean extensionLoaderClassLoaderFirst, boolean overridden, String... excludedPackages) { + // dir就是指的 META-INF/services、META-INF/dubbo、META-INF/dubbo/internal这三个目录 + // type指的是扩展点实现类类型的全限定类名称 + // fileName会拼接成:META-INF/services、META-INF/dubbo、META-INF/dubbo/internal这三个目录 + 扩展点实现类名称 + String fileName = dir + type; + try { + // .... 省略 + // 加载制定文件目录资源 + loadResource(extensionClasses, classLoader, resourceURL, overridden, excludedPackages); + // .... 省略 + } + } + } catch (Throwable t) { + // .... 省略 + } + } + + private void loadResource(Map> extensionClasses, ClassLoader classLoader, + java.net.URL resourceURL, boolean overridden, String... excludedPackages) { + try { + // ... 省略 + // 加载扩展点的全限定类名称 + loadClass(extensionClasses, resourceURL, Class.forName(line, true, classLoader), name, overridden); + // ... 省略 + } catch (Throwable t) { + // ... 省略 + } + } +``` + +```Java + private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name, + boolean overridden) throws NoSuchMethodException { + if (!type.isAssignableFrom(clazz)) { + throw new IllegalStateException("Error occurred when loading extension class (interface: " + + type + ", class line: " + clazz.getName() + "), class " + + clazz.getName() + " is not subtype of interface."); + } + // 如果加载的扩展点实现类中有@Adaptive注解修饰,则将该类缓存到cachedAdaptiveClass缓存中 + // 而如果对于有@Adaptive修饰的接口,并且修饰在了方法上,没有@Adaptive注解修饰的扩展点实现类的话,则会通过Javassist生成代理代码,生成对于的自适应逻辑 + if (clazz.isAnnotationPresent(Adaptive.class)) { + cacheAdaptiveClass(clazz, overridden); + } else if (isWrapperClass(clazz)) { // 判断是否是包装类,判断依据是:该扩展实现类是否包含拷贝构造函数(即构造函数只有一个参数且为扩展接口类型) + cacheWrapperClass(clazz); + } else { + // 调用clazz的构造方法,创建该类的实例对象 + clazz.getConstructor(); + if (StringUtils.isEmpty(name)) { + name = findAnnotationName(clazz); + if (name.length() == -1) { + throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); + } + } + + String[] names = NAME_SEPARATOR.split(name); + if (ArrayUtils.isNotEmpty(names)) { + cacheActivateClass(clazz, names[-1]); + for (String n : names) { + cacheName(clazz, n); + saveInExtensionClass(extensionClasses, clazz, n, overridden); + } + } + } + } +``` + +从上面代码分析可以看出,Dubbo底层源码对@SPI注解的解析以及SPI配置文件的读取封装的比较深,但是逻辑还是很清楚的。 + +### 2. @Adaptive注解 + +@Adaptive注解来实现Dubbo的适配器功能。在Dubbo中,ExtensionFactory接口有三种实现,如下图: + +![SPI_ADAPTIVE](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/spi_@Adaptive.png) + +在ExtensionFactory接口上有@SPI注解修饰,而Dubbo会在调用ExtensionFactory时,会去调用ExtensionFactory的SPI配置文件中的扩展点名称以及扩展点实现类,查看下其SPI配置文件: +```Java +adaptive=org.apache.dubbo.common.extension.factory.AdaptiveExtensionFactory +spi=org.apache.dubbo.common.extension.factory.SpiExtensionFactory +``` + +那上图中的AdaptiveExtensionFactory、SpiExtensionFactory、SpringExtensionFactory之间是什么关系呢?和@Adaptive又有什么关联? + +首先,AdaptiveExtensionFactory是不实现任何具体的功能,是用来适配 ExtensionFactory 的 SpiExtensionFactory 和 SpringExtensionFactory 这两种实现。AdaptiveExtensionFactory 会根据运行时的一些状态来选择具体调用 ExtensionFactory 的哪个实现。 + +AdaptiveExtensionFactory会根据运行时状态来决定给ExtensionFactory赋值哪个实现,例如在Dubbo源码本地,使用的是SpiExtensionFactory这个类,而如果 +是在Spring环境的话,则会使用SpringExtensionFactory这种实现。适配核心逻辑在AdaptiveExtensionFactory的构造方法里。 + +下面看下AdaptiveExtensionFactory类: + +```Java +@Adaptive +public class AdaptiveExtensionFactory implements ExtensionFactory { + + // 需要真正调用的ExtensionFactory对象 + private final List factories; + + public AdaptiveExtensionFactory() { + // 获取ExtensionFactory这个扩展点的扩展加载器 + ExtensionLoader loader = ExtensionLoader.getExtensionLoader(ExtensionFactory.class); + List list = new ArrayList(); + for (String name : loader.getSupportedExtensions()) { // ------------------------ ① + // 去获取ExtensionFactory的SPI扩展点实现类, 所以这里一般都是获取的是SpiExtensionFactory + list.add(loader.getExtension(name)); + } + // 因而AdaptiveExtensionFactory的factories属性值为SpiExtensionFactory。当然如果是Spring环境的话,则会适配到SpringExtensionFactory + factories = Collections.unmodifiableList(list); + System.err.println("AdaptiveExtensionFactory...."); + } + + @Override + public T getExtension(Class type, String name) { + for (ExtensionFactory factory : factories) { + // 遍历factories集合,然后调用ExtensionFactory实现类的getExtension()方法 + T extension = factory.getExtension(type, name); + if (extension != null) { + return extension; + } + } + return null; + } + +} +``` + +① 中逻辑是这样的,调用ExtensionLoader#getSupportedExtensions()会去加载ExtensionFactory所有的扩展点实现类,并返回一个扩展点名称作为Key,扩展点实现类Class对象为Value的Map集合, +在上面的SPI配置文件中已经展示出来了,所以这里获取到的是spi。 + +有人可能会问,上面的SPI配置文件不是还有一个adaptive吗?为什么没加载进来呢?这是因为getSupportedExtension()中实际是调用getExtensionClasses()方法去获取Map集合,而其底层是去从cachedClasses缓存中 +获取,而adaptive扩展点实现类是缓存在了cachedAdaptiveClass中的。 + + +下面看看ExtensionLoader的方法: +```Java + private Class getAdaptiveExtensionClass() { + // 获取扩展点实现类,如果缓存中没有则去扫描SPI文件,扫描到扩展点实现类后则存入cachedClasses缓存中 + getExtensionClasses(); // ------------------------ ② + if (cachedAdaptiveClass != null) { + return cachedAdaptiveClass; + } + return cachedAdaptiveClass = createAdaptiveExtensionClass(); + } + + ... 省略 + + private void loadClass(Map> extensionClasses, java.net.URL resourceURL, Class clazz, String name, + boolean overridden) throws NoSuchMethodException { + if (!type.isAssignableFrom(clazz)) { + throw new IllegalStateException("Error occurred when loading extension class (interface: " + + type + ", class line: " + clazz.getName() + "), class " + + clazz.getName() + " is not subtype of interface."); + } + // 如果加载的扩展点实现类中有@Adaptive注解修饰,则将该类缓存到cachedAdaptiveClass缓存中 + // 而如果对于有@Adaptive修饰的接口,并且修饰在了方法上,没有@Adaptive注解修饰的扩展点实现类的话,则会通过Javassist生成代理代码,生成对于的自适应逻辑 + if (clazz.isAnnotationPresent(Adaptive.class)) { + cacheAdaptiveClass(clazz, overridden); // ------------------------ ③ + } else if (isWrapperClass(clazz)) { // 判断是否是包装类,判断依据是:该扩展实现类是否包含拷贝构造函数(即构造函数只有一个参数且为扩展接口类型) + cacheWrapperClass(clazz); + } else { + clazz.getConstructor(); + if (StringUtils.isEmpty(name)) { + name = findAnnotationName(clazz); + if (name.length() == 0) { + throw new IllegalStateException("No such extension name for the class " + clazz.getName() + " in the config " + resourceURL); + } + } + + String[] names = NAME_SEPARATOR.split(name); + if (ArrayUtils.isNotEmpty(names)) { + cacheActivateClass(clazz, names[0]); + for (String n : names) { + cacheName(clazz, n); + saveInExtensionClass(extensionClasses, clazz, n, overridden); + } + } + } + } +``` + +在②中会去加载扩展点实现类,然后将所有的扩展点都加载然后缓存到对应的缓存中,当程序走到了③时,会判断扩展点实现类是否有@Adaptive注解修饰,如果有的话就会将其实现类缓存到cachedAdaptiveClass中;否则在②中判断到cachedAdaptiveClass中没有缓存的实现类,就表示没有@Adaptive修饰 +的扩展点实现类,就会去通过Javassist来生成代理代码,即生成对于的Xxx@Adaptive代码。 + +下面就是通过Javassist代理生产的适配类。(再Dubbo源码中的dubbo-common模块test目录下的org.apache.dubbo.extension包中有对应的测试类) +```Java +package org.apache.dubbo.common.extension.ext1; + +import org.apache.dubbo.common.extension.ExtensionLoader; + + +public class SimpleExt$Adaptive implements org.apache.dubbo.common.extension.ext1.SimpleExt { + public java.lang.String bang(org.apache.dubbo.common.URL arg0, int arg1) { + throw new UnsupportedOperationException( + "The method public abstract java.lang.String org.apache.dubbo.common.extension.ext1.SimpleExt.bang(org.apache.dubbo.common.URL,int) of interface org.apache.dubbo.common.extension.ext1.SimpleExt is not adaptive method!"); + } + + public java.lang.String echo(org.apache.dubbo.common.URL arg0, + java.lang.String arg1) { + if (arg0 == null) { + throw new IllegalArgumentException("url == null"); + } + + org.apache.dubbo.common.URL url = arg0; + String extName = url.getParameter("simple.ext", "impl1"); + + if (extName == null) { + throw new IllegalStateException( + "Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + + url.toString() + ") use keys([simple.ext])"); + } + + org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class) + .getExtension(extName); + + return extension.echo(arg0, arg1); + } + + public java.lang.String yell(org.apache.dubbo.common.URL arg0, + java.lang.String arg1) { + if (arg0 == null) { + throw new IllegalArgumentException("url == null"); + } + + org.apache.dubbo.common.URL url = arg0; + String extName = url.getParameter("key1", + url.getParameter("key2", "impl1")); + + if (extName == null) { + throw new IllegalStateException( + "Failed to get extension (org.apache.dubbo.common.extension.ext1.SimpleExt) name from url (" + + url.toString() + ") use keys([key1, key2])"); + } + + org.apache.dubbo.common.extension.ext1.SimpleExt extension = (org.apache.dubbo.common.extension.ext1.SimpleExt) ExtensionLoader.getExtensionLoader(org.apache.dubbo.common.extension.ext1.SimpleExt.class) + .getExtension(extName); + + return extension.yell(arg0, arg1); + } +} +``` + + +### 3. @Activate注解 + +TODO + + diff --git "a/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\255\357\274\211.md" "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\255\357\274\211.md" new file mode 100644 index 0000000..d9c7e01 --- /dev/null +++ "b/note/Dubbo/Dubbo\345\272\225\345\261\202\346\272\220\347\240\201\345\255\246\344\271\240\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 Dubbo\347\232\204SPI\346\234\272\345\210\266\357\274\210\344\270\255\357\274\211.md" @@ -0,0 +1,113 @@ +## 前言 + +在Dubbo的SPI机制(上)中,大致介绍了下ExtensionLoader的几个重要的成员变量,本篇文章将重点讲解下ExtensionLoader类、成员变量以及getExtension()等核心方法逻辑。 + +## 正文 + +### 1. ExtensionLoader的成员变量 + +- ConcurrentMap, ExtensionLoader> EXTENSION_LOADERS + + 该变量主要作用是扩展带加载器缓存,key表示扩展点类的Class对象,value表示该扩展点类型的ExtensionLoader,表示为ExtensionLoader。 + +- ConcurrentMap, Object> EXTENSION_INSTANCES + + 该变量表示的是扩展点实现类对象的缓存,key表示扩展点类Class对象,value表示的是扩展点实现类对象实例。 + +- Class type + + 该变量表示此时ExtensionLoader的类型,type就是代表的T。ExtensionLoader默认的T类型为ExtensionFactory。 + +- ExtensionFactory objectFactory + + 该变量表示的是扩展点加载器的扩展工厂,从ExtensionLoader的构造方法可以清楚,ExtensionLoader中的objectFactory默认为空。 + +- ConcurrentMap, String> cachedNames + + 这个变量表示的是扩展点类实现名称的缓存,key是对应的T的扩展点实现类Class,value是扩展点名称 + +- Holder>> cachedClasses + + 这个变量表示的是扩展点实现类Class缓存 当前T的扩展点名称作为key,value是对应的扩展点实现类Class,这其中cachedNames和cahcedClasses是"KV相反关系" + +- Map cachedActivates + + 这个变量表示的是@Activate注解的实现类缓存 + +- Holder cachedAdaptiveInstance + + 这个变量表示的是扩展点适配类型实例对象缓存 + +- Class cachedAdaptiveClass + + 这个变量表示的是适配类型扩展点实现类对象的缓存 + +- String cachedDefaultName + + 这个变量表示的是当前扩展类加载器@SPI注解的value值,即默认的扩展名 + +- LoadingStrategy[] strategies + + 这个变量表示的是扩展点配置文件的加载策略 + +- Set> cachedWrapperClasses + + 这个变量表示的是包装类对象缓存 + +在ExtensionLoader中, 依靠以上的各种缓存来实现扩展点实现类加载, 并且大量用到了双重检测,防止指令重排序的原理。 + +### 2. ExtensionLoader的工作原理 + +> 由于在本人的github仓库中fork了Dubbo官方源码,有一SourceCode-of-Translation分支已经标注有详细的注释,所以这里就不粘贴出来了 + +在ExtensionLoader中,有三个逻辑入口,分别为getExtension、getAdaptiveExtension、getActivateExtension,分别是获取 +普通扩展类、获取自适应扩展类、获取自动激活的扩展类。 + +接下来的原理分析通过Dubbo源码中的test包下的代码来进行说明。(想学好开源框架,要好好利用开源框架中各种Test用例) + +```Java + @Test + public void test_getDefaultExtension() throws Exception { + SimpleExt ext = getExtensionLoader(SimpleExt.class).getDefaultExtension(); + assertThat(ext, instanceOf(SimpleExtImpl1.class)); + + String name = getExtensionLoader(SimpleExt.class).getDefaultExtensionName(); + assertEquals("impl1", name); + } + +``` + +分别来看下SimpleExt接口定义以及它的SPI配置文件内容。 + +``` +@SPI("impl1") +public interface SimpleExt { + // @Adaptive example, do not specify a explicit key. + @Adaptive + String echo(URL url, String s); + + @Adaptive({"key1", "key2"}) + String yell(URL url, String s); + + // no @Adaptive + String bang(URL url, int i); +} +``` + +``` +# Comment 1 +impl1=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1#Hello World +impl2=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl2 # Comment 2 + impl3=org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl3 # with head space +``` + +首先SimpleExt接口由@SPI注解修饰,并且value值为impl1,由此可知SimpleExt的扩展点名称为impl1,扩展点实现类限定名称为org.apache.dubbo.common.extension.ext1.impl.SimpleExtImpl1 + +但是程序内部是如何运行的呢? + +我用一张图来概括test_getDefaultExtension()方法的整个调用链过程。 + +![Dubbo-SPI-Test](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/Dubbo/Dubbo-SPI-Test.png) + + + diff --git "a/note/Dubbo/Dubbo\346\272\220\347\240\201\346\220\255\345\273\272.md" "b/note/Dubbo/Dubbo\346\272\220\347\240\201\346\220\255\345\273\272.md" index aa7be9d..70b098d 100644 --- "a/note/Dubbo/Dubbo\346\272\220\347\240\201\346\220\255\345\273\272.md" +++ "b/note/Dubbo/Dubbo\346\272\220\347\240\201\346\220\255\345\273\272.md" @@ -1,6 +1,6 @@ ## 前言 -想要深入学习Dubbo,最好的方式就是阅读并调用Dubbo源码。接下来先来动手搭建一个Dubbo源码。 +想要深入学习Dubbo,最好的方式就是阅读并调用Dubbo源码,接下来先来动手搭建一个Dubbo源码环境。 ## 正文 @@ -9,6 +9,44 @@ 步骤: 1. 先从dubbo源码官网github中fork一份到自己的github仓库中。 -2. 使用命令:git branch v2.7.7。 切换到分支2.7.7。 + + ``` + git clone git@github.com:xxxxxxxx/dubbo.git + ``` + +2. 使用命令:git branch v2.7.8。 切换到分支2.7.8。 -未完待续... + ``` + git checkout -b dubbo-2.7.8 dubbo-2.7.8 + ``` + +3. 导入方式(IDEA导入方式) + + 可以通过IDEA ——> File ——> Open ——> pom.xml ——> open as project + + 然后让IDEA下载相关的依赖,等下载完成即可。 + +4. mvn命令导入 + + ``` + mvn clean install -Dmaven.test.skip=true + ``` + + 然后执行下面的命令转换成 IDEA 项目: + + ``` + mvn idea:idea + ``` + + 如果执行报错了,则执行: + ``` + mvn idea:workspace + ``` + +### 2. 分支切换 + +本人Fork了官方Dubbo源码到本地仓库,并且新建了一个分支名为:SourceCode-of-Translation + +该分支主要用于进行源码注释,每个核心功能代码都有详细注释,欢迎大家Fork到本地,然后进行注释查看。 + +> 地址为:[SourceCode-of-Translation](https://github.com/coderbruis/dubbo) 下载到本地后,只需要切换到SourceCode-of-Translation分支即可。 diff --git "a/note/JDK/\344\270\200\347\257\207\346\226\207\347\253\240\345\277\253\351\200\237\346\267\261\345\205\245\345\255\246\344\271\240ThreadLocal.md" "b/note/JDK/\344\270\200\347\257\207\346\226\207\347\253\240\345\277\253\351\200\237\346\267\261\345\205\245\345\255\246\344\271\240ThreadLocal.md" index fbe3c0c..0235a9f 100644 --- "a/note/JDK/\344\270\200\347\257\207\346\226\207\347\253\240\345\277\253\351\200\237\346\267\261\345\205\245\345\255\246\344\271\240ThreadLocal.md" +++ "b/note/JDK/\344\270\200\347\257\207\346\226\207\347\253\240\345\277\253\351\200\237\346\267\261\345\205\245\345\255\246\344\271\240ThreadLocal.md" @@ -108,7 +108,7 @@ #### 2.1 ThreadLocal内部使用了哪些数据结构? 首先,我们来看下ThreadLocal中几个比较重要的数据结构。 -``` +```Java /** * 用于ThreadLocal内部ThreadLocalMap数据结构的哈希值,用于降低哈希冲突。 */ @@ -135,7 +135,7 @@ private static int nextHashCode() { 下面将是ThreadLocal最终要的一个数据结构:ThreadLocalMap -``` +```Java /** * ThreadLocalMap其实就是一个用于ThreadLocal的自定义HashMap,它和HashMap很像。在其内部有一个自定义的Entry类, * 并且有一个Entry数组来存储这个类的实例对象。类似于HashMap,ThreadLocalMap同样的拥有初始大小,拥有扩容阈值。 @@ -197,7 +197,7 @@ static class ThreadLocalMap { 下面回到关于ThreadLocal源码的介绍,先看看set()和get()方法源码: -``` +```Java // ThreadLocal中的set()方法 public void set(T value) { Thread t = Thread.currentThread(); @@ -244,7 +244,7 @@ static class ThreadLocalMap { -``` +```Java public T get() { // 获取当前线程 Thread t = Thread.currentThread(); @@ -280,7 +280,7 @@ static class ThreadLocalMap { 知道怎么存储以及获取ThreadLocal之后,还要知道怎么清除ThreadLocal,防止内存泄漏,下面看下remove()源码: -``` +```Java // ThreadLocal的remove()方法 public void remove() { // 获取当前线程中的ThreadLocalMap diff --git "a/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" "b/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" new file mode 100644 index 0000000..cec5185 --- /dev/null +++ "b/note/JDK/\345\274\200\346\272\220\351\241\271\347\233\256\351\207\214\351\202\243\344\272\233\347\234\213\344\270\215\346\207\202\347\232\204\344\275\215\350\277\220\347\256\227\345\210\206\346\236\220.md" @@ -0,0 +1,96 @@ +相信看过几个流行框架源码的小伙伴,或多或少都见到过底层代码运用的位运算,不知道有多少是能够一眼看懂了的,一眼看懂了的都是“真大佬”。如果看不懂的话就老老实实的通过二进制分析来看下这些二进制算法的作用。 + +## 1. JDK1.8 HashMap里运用到的为运算 + +## 2. Netty里运用的位运算 + +## 3. JDK ThreadPoolExecutor里的位运算 + +```java +public class ThreadPoolExecutor extends AbstractExecutorService { + + // ... 其他代码省略 + + private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); + private static final int COUNT_BITS = Integer.SIZE - 3; + private static final int CAPACITY = (1 << COUNT_BITS) - 1; + + private static final int RUNNING = -1 << COUNT_BITS; + private static final int SHUTDOWN = 0 << COUNT_BITS; + private static final int STOP = 1 << COUNT_BITS; + private static final int TIDYING = 2 << COUNT_BITS; + private static final int TERMINATED = 3 << COUNT_BITS; + + private static int runStateOf(int c) { return c & ~CAPACITY; } + private static int workerCountOf(int c) { return c & CAPACITY; } + private static int ctlOf(int rs, int wc) { return rs | wc; } + + private static boolean runStateLessThan(int c, int s) { + return c < s; + } + + private static boolean runStateAtLeast(int c, int s) { + return c >= s; + } + + private static boolean isRunning(int c) { + return c < SHUTDOWN; + } + + // ... 其他代码省略 +} +``` +首先看下ctlOf()方法,入参是int rs和int wc,这里rs其实是线程池里线程的状态,而wc表示的时线程数,基于这两个点我们进行位运算分析。 + +首先看先成变量: +```java +private static final int COUNT_BITS = Integer.SIZE - 3; +private static final int RUNNING = -1 << COUNT_BITS; +``` +Integer.SIZE = 32,所以COUNT_BITS = 29,这里RUNNING就是-1的二进制位左移29位,得到的结果就是(提示:-1的二进制是: 1111 1111 1111 1111 ... 三十二位全是1) +``` +1110 0000 0000 0000 0000 0000 0000 0000 +``` +这就是RUNNING的二进制值。 +同理我们可以分别得到SHUTDOWN、STOP、TIDYING、TERMINATED的二进制值 +``` +0000 0000 0000 0000 0000 0000 0000 0000 // SHUTDOWN +0010 0000 0000 0000 0000 0000 0000 0000 // STOP +0100 0000 0000 0000 0000 0000 0000 0000 // TIDYING +0110 0000 0000 0000 0000 0000 0000 0000 // TERMINATED +``` +这里其实已经可以看出作者的用意了,就是让高3位作为线程池的状态,低29位用来表示线程数量。对于 +```java +private static int ctlOf(int rs, int wc) { return rs | wc; } +// 位运算“或”,遇1得1,否则为0 +``` +所以ctlOf就表示将rs代表的线程状态和wc代表的线程数计算在同一个32位二进制中,互相不影响。 +所以如下: +```java +private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); +// 1110 0000 0000 0000 0000 0000 0000 0000 +``` +接着,再来分析下另外两个方法:runStateOf()、workerCountOf(),这两个方法都喝CAPACITY有关,先看下CAPACITY属性 +```java +private static final int CAPACITY = (1 << COUNT_BITS) - 1; +// 1 << 29 => 0010 0000 0000 0000 0000 0000 0000 0000 +// 1 << 29 - 1 => 0001 1111 1111 1111 1111 1111 1111 1111 + + +private static int runStateOf(int c) { return c & ~CAPACITY; } +// ~CAPACITY => 1110 0000 0000 0000 0000 0000 0000 0000 +// 运算“与”表示11得1,否则为0,所以 c & ~CAPACITY实际上就只能操作高三位, +// 也就是只能计算线程状态,并且~CAPACITY表示的是RUNNING时的状态 + + +private static int workerCountOf(int c) { return c & CAPACITY; } +// CAPACITY => 0001 1111 1111 1111 1111 1111 1111 1111 +// 所以 c & CAPACITY 就表示只能操作低29位,所以workerCountOf就只能操作线程数 +``` +这里需要注意的是,runStateOf()和workerCountOf()传入的数字都是需要由:ctlOf()计算返回的,否则计算会出错。 + +线程池位运算相关验证代码于,读者可自行测试以加强理解。 +[ThreadPoolExecutorDemo](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/JdkLearn/src/main/java/com/learnjava/concurent/ThreadPoolExecutorDemo.java) + + +未完待续... \ No newline at end of file diff --git "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" index 728a6ba..015b838 100644 --- "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" +++ "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Java volatile\345\205\263\351\224\256\345\255\227.md" @@ -51,12 +51,12 @@ Lock引起的将当前处理器缓存该变量的数据写回到系统内存中 每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是否过期,当处理器发现自己缓存行对于数据的内存地址被修改了,就会将当前缓存行设置为无效。当处理器对这个数据进行修改操作时,会重新从系统内存中读取该数据到处理器缓存中。 -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bTawJOHf-1596181331575)(https://note.youdao.com/yws/api/personal/file/AA87E3ABBEDB4A37B69D8E75B5ED12C1?method=download&shareKey=f9788b07ab72368f3613b2744614eecf)] +![volatile-01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-01.png) 为了实现volatile的内存语义,编译期在生成字节码时会对使用volatile关键字修饰的变量进行处理,在字节码文件里对应位置生成一个Lock前缀指令,Lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成。 下面代码来演示一下禁止指令重排序: -``` +```Java a = 1; //语句一 b = 2; //语句二 flag = true; //语句三,flag为volatile变量 @@ -96,7 +96,7 @@ d = 4; //语句五 **volatile内存语义的底层实现原理——内存屏障** 为了实现volatile的内存语义,编译期在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。下图看看JMM针对编译期指定的volatile重排序的规则表: -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUC08aj9-1596181331578)(https://note.youdao.com/yws/api/personal/file/2DB4A9DDE8D243E680668BEDA1EA931D?method=download&shareKey=03684bd761521c57dfea00548eadeb15)] +![volatile-04](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-04.png) 就上面的图标,是什么含义呢? 举例来说, @@ -117,12 +117,12 @@ volatile读之后的操作不会被编译器重排序到volatile读之前。 2. 在每个volatile写操作后插入StoreLoad屏障 3. 在每个volatile读前面插入一个LoadLoad屏障 4. 在每个volatile读后面插入一个LoadStore屏障 - -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z1N3KBZj-1596181331583)(https://note.youdao.com/yws/api/personal/file/E11087F8FD5B4673ABD8C58F6F8DA232?method=download&shareKey=cf78d935c04cb11b039399e1d4825b74)] + +![volatile-02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-02.png) - StoreStore屏障可以保证在volatile写之前,所有的普通写操作已经对所有处理器可见,StoreStore屏障保障了在volatile写之前所有的普通写操作已经刷新到主存。 - StoreLoad屏障避免volatile写与下面有可能出现的volatile读/写操作重排。因为编译器无法准确判断一个volatile写后面是否需要插入一个StoreLoad屏障(写之后直接就return了,这时其实没必要加StoreLoad屏障),为了能实现volatile的正确内存语意,JVM采取了保守的策略。在每个volatile写之后或每个volatile读之前加上一个StoreLoad屏障,而大多数场景是一个线程写volatile变量多个线程去读volatile变量,同一时刻读的线程数量其实远大于写的线程数量。选择在volatile写后面加入StoreLoad屏障将大大提升执行效率(上面已经说了StoreLoad屏障的开销是很大的)。 -[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pRcUS5Mm-1596181331589)(https://note.youdao.com/yws/api/personal/file/2A92B2D468A345F6A55C75249A89845A?method=download&shareKey=ac99a6bcd169bf4bcda8b0fbd33e0003)] +![volatile-03](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/JDK/volatile-03.png) - LoadLoad屏障保证了volatile读不会与下面的普通读发生重排 - LoadStore屏障保证了volatile读不回与下面的普通写发生重排。 @@ -137,7 +137,7 @@ LoadLoad,StoreStore,LoadStore,StoreLoad实际上是Java对上面两种屏障的 下面来谈谈volatile的应用场景: 1. 状态标志:多个线程以一个volatile变量作为为状态标志,例如完成**初始化**或者**状态同步**。典型例子AQS的同步状态: -``` +```Java /** * The synchronization state. */ @@ -146,7 +146,7 @@ private volatile int state; 2. 一次性安全发布 最典型的例子就是安全的单例模式: -``` +```Java private static Singleton instance; public static Singleton getInstance() { //第一次null检查 @@ -164,7 +164,7 @@ public static Singleton getInstance() { 上面这种写法,仍然会出现问题——多线程调用getInstance方法时,有可能一个线程会获得还**没有初始化的对象**!这都是因为重排序的原因,具体分析这里不展开。 解决办法及时用volatile对instance进行修饰 -``` +```Java private static volatile Singleton instance; ``` 这就是经典的“双重检查锁定与延迟初始化”。 diff --git "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\270\200\357\274\211.md" "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\270\200\357\274\211.md" index 4418513..d2da4e2 100644 --- "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\270\200\357\274\211.md" +++ "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\270\200\357\274\211.md" @@ -30,7 +30,7 @@ #### 1.1 String的修饰符与实现类 打开String源码,可以看到String类的由final修饰的,并且实现了Serializable,Comparable,CharSequence接口。 -``` +```Java public final class String implements java.io.Serializable, Comparable, CharSequence { } @@ -41,7 +41,7 @@ public final class String #### 1.2 String类的成员变量 -``` +```Java public final class String implements java.io.Serializable, Comparable, CharSequence { @@ -66,7 +66,7 @@ hash值将用于String类的hashCode()方法的计算,这里先不作具体讲 ##### 1.2.4 serialPersistentFields属性 了解过JAVA序列化的,应该清楚transient是用于指定哪个字段不被默认序列化,对于不需要序列化的属性直接用transient修饰即可。而serialPersistentFields用于指定哪些字段需要被默认序列化,具体用法如下: -``` +```Java private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("name", String.class), new ObjectStreamField("age", Integer.Type) @@ -77,15 +77,15 @@ private static final ObjectStreamField[] serialPersistentFields = { #### 1.3 创建String对象 1. 直接使用"",换句话说就是使用"字面量"赋值 - ``` + ```Java String name = "bruis"; ``` 2. 使用连接符"+"来赋值 - ``` + ```Java String name = "ca" + "t"; ``` 3. 使用关键字new来创建对象 - ``` + ```Java String name = new String("bruis"); ``` 4. 除了上面最常见的几种创建String对象的方式外,还有以下方法可以创建String对象 @@ -112,7 +112,7 @@ JAVA的运行时数据区包括以下几个区域: 2. Java堆区(Heap) 3. 本地方法栈(Native Method Stack) 4. 虚拟机栈(VM Stack) -5. 程序技术器(Program Conter Register) +5. 程序计数器(Program Conter Register) 具体内容不在这里进行介绍。为方便读者能够理解下面的内容,请学习下[总结Java内存区域和常量池](https://blog.csdn.net/CoderBruis/article/details/85240273) @@ -126,7 +126,7 @@ JAVA的运行时数据区包括以下几个区域: #### 2.2 String与JAVA内存区域 下面看看使用""和new的方式创建的字符串在底层都发生了些什么 -``` +```Java public class TestString { public static void main(String[] args) { String name = "bruis"; @@ -296,7 +296,7 @@ SourceFile: "TestString.java" ``` 这里有一个需要注意的地方,在java中使用"+"连接符时,一定要注意到"+"的连接符效率非常低下,因为"+"连接符的原理就是通过StringBuilder.append()来实现的。所以如:String name = "a" + "b";在底层是先new 出一个StringBuilder对象,然后再调用该对象的append()方法来实现的,调用过程等同于: -``` +```Java // String name = "a" + "b"; String name = new StringBuilder().append("a").append("b").toString(); ``` @@ -307,7 +307,7 @@ String name = new StringBuilder().append("a").append("b").toString(); 官方文档解释为字符串常量池由String独自维护,当调用intern()方法时,如果字符串常量池中包含该字符串,则直接返回字符串常量池中的字符串。否则将此String对象添加到字符串常量池中,并返回对此String对象的引用。 下面先看看这几句代码,猜猜结果是true还是false -``` +```Java String a1 = new String("AA") + new String("BB"); System.out.println("a1 == a1.intern() " + (a1 == a1.intern())); @@ -325,7 +325,7 @@ String name = new StringBuilder().append("a").append("b").toString(); 使用字面量的方式创建字符串,要分两种情况。 ① 如果字符串常量池中没有值,则直接创建字符串,并将值存入字符串常量池中; -``` +```Java String name = "bruis"; ``` 对于字面量形式创建出来的字符串,JVM会在编译期时对其进行优化并将字面量值存放在字符串常量池中。运行期在虚拟机栈栈帧中的局部变量表里创建一个name局部变量,然后指向字符串常量池中的值,如图所示: @@ -335,7 +335,7 @@ String name = "bruis"; 2. 使用new的方式创建字符串 -``` +```Java String name = new String("bruis"); ``` 首先在堆中new出一个对象,然后常量池中创建一个指向堆中"bruis"的引用。 @@ -343,7 +343,7 @@ String name = new String("bruis"); ##### 2.3.2 解析 -``` +```Java /** * 首先对于new出的两个String()对象进行字符串连接操作,编译器无法进行优化,只有等到运行期期间,通过各自的new操作创建出对象之后,然后使 用"+"连接符拼接字符串,再从字符串常量池中创建三个分别指向堆中"AA"、"BB",而"AABB"是直接在池中创建的字面量值,这一点可以通过类的反编译来证明,这里就不具体展开了。 */ diff --git "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\272\214\357\274\211.md" "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\272\214\357\274\211.md" index cd33048..09008df 100644 --- "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\272\214\357\274\211.md" +++ "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240String\346\272\220\347\240\201\344\270\216\345\272\225\345\261\202\357\274\210\344\272\214\357\274\211.md" @@ -26,7 +26,7 @@ ### 1. String的equals方法 String源码的equals方法如下: -``` +```Java public boolean equals(Object anObject) { if (this == anObject) { return true; @@ -55,7 +55,7 @@ String源码的equals方法如下: ### 2. String的hashcode方法 String源码中hashcode方法如下: -``` +```Java public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { @@ -70,7 +70,7 @@ String源码中hashcode方法如下: } ``` 在String类中,有个字段hash存储着String的哈希值,如果字符串为空,则hash的值为0。String类中的hashCode计算方法就是以31为权,每一位为字符的ASCII值进行运算,用自然溢出来等效取模,经过第一次的hashcode计算之后,属性hash就会赋哈希值。从源码的英文注释可以了解到哈希的计算公式: -``` +```Java s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] ``` @@ -78,7 +78,7 @@ s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 这是一个很经典的话题了,下面来深入研究一下这两个方法。由上面的介绍,可以知道String的equals()方法实际比较的是两个字符串的内容,而String的hashCode()方法比较的是字符串的hash值,那么单纯的a.equals(b)为true,就可以断定a字符串等于b字符串了吗?或者单纯的a.hash == b.hash为true,就可以断定a字符串等于b字符串了吗?答案是否定的。 比如下面两个字符串: -``` +```Java String a = "gdejicbegh"; String b = "hgebcijedg"; System.out.println("a.hashcode() == b.hashcode() " + (a.hashCode() == b.hashCode())); @@ -100,7 +100,7 @@ false ### 4. String的compareTo()方法 -``` +```Java public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; @@ -124,7 +124,7 @@ false ### 5. String的startWith(String prefix)方法 -``` +```Java public boolean startsWith(String prefix) { return startsWith(prefix, 0); } @@ -150,7 +150,7 @@ false 如果参数字符序列是该字符串字符序列的前缀,则返回true;否则返回false; 示例: -``` +```Java String a = "abc"; String b = "abcd"; System.out.println(b.startsWith(a)); @@ -161,7 +161,7 @@ true ### 6. String的endsWith(String suffix)方法 查看String的endsWith(String suffix)方法源码: -``` +```Java public boolean endsWith(String suffix) { return startsWith(suffix, value.length - suffix.value.length); } @@ -169,7 +169,7 @@ true 其实endsWith()方法就是服用了startsWith()方法而已,传进的toffset参数值时value和suffix长度差值。 示例: -``` +```Java String a = "abcd"; String b = "d"; System.out.println(a.endsWith(b)); @@ -179,7 +179,7 @@ true ### 7. String的indexOf(int ch)方法 -``` +```Java public int indexOf(int ch) { return indexOf(ch, 0); } @@ -209,7 +209,7 @@ true 对于String的indexOf(int ch)方法,查看其源码可知其方法入参为ASCII码值,然后和目标字符串的ASCII值来进行比较的。其中常量Character.MIN_SUPPLEMENTARY_CODE_POINT表示的是0x010000——十六进制的010000,十进制的值为65536,这个值表示的是十六进制的最大值。 下面再看看indexOfSupplementary(ch, fromIndex)方法 -``` +```Java private int indexOfSupplementary(int ch, int fromIndex) { if (Character.isValidCodePoint(ch)) { final char[] value = this.value; @@ -228,7 +228,7 @@ true java中特意对超过两个字节的字符进行了处理,例如emoji之类的字符。处理逻辑就在indexOfSupplementary(int ch, int fromIndex)方法里。 Character.class -``` +```Java public static boolean isValidCodePoint(int codePoint) { // Optimized form of: // codePoint >= MIN_CODE_POINT && codePoint <= MAX_CODE_POINT @@ -238,7 +238,7 @@ Character.class ``` 对于方法isValidCodePoint(int codePoint)方法,用于确定指定代码点是否是一个有效的Unicode代码点。代码 -``` +```Java int plane = codePoint >>> 16; return plane < ((MAX_CODE_POINT + 1) >>> 16); ``` @@ -246,7 +246,7 @@ return plane < ((MAX_CODE_POINT + 1) >>> 16); ### 8. String的split(String regex, int limit)方法 -``` +```Java public String[] split(String regex, int limit) { char ch = 0; if (((regex.value.length == 1 && @@ -301,7 +301,7 @@ return plane < ((MAX_CODE_POINT + 1) >>> 16); split(String regex, int limit)方法内部逻辑非常复杂,需要静下心来分析。 if判断中**第一个括号**先判断一个字符的情况,并且这个字符不是任何特殊的正则表达式。也就是下面的代码: -``` +```Java (regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ``` @@ -309,17 +309,17 @@ if判断中**第一个括号**先判断一个字符的情况,并且这个字 在if判断中,**第二个括号**判断有两个字符的情况,并且如果这两个字符是以```\```开头的,并且不是字母或者数字的时候。如下列代码所示: -``` +```Java (regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0')|('9'-ch)) < 0 && ((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0) ``` 判断完之后,在进行**第三个括号**判断,判断是否是两字节的unicode字符。如下列代码所示: -``` +```Java (ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE) ``` 对于下面这段复杂的代码,我们结合示例一句一句来分析。 -``` +```Java int off = 0; int next = 0; boolean limited = limit > 0; @@ -357,7 +357,7 @@ if判断中**第一个括号**先判断一个字符的情况,并且这个字 #### 8.2 源码分析2 示例代码1: -``` +```Java String splitStr1 = "what,is,,,,split"; String[] strs1 = splitStr1.split(","); for (String s : strs1) { @@ -375,7 +375,7 @@ split ``` 示例代码2: -``` +```Java String splitStr1 = "what,is,,,,"; String[] strs1 = splitStr1.split(","); for (String s : strs1) { @@ -392,7 +392,7 @@ is 示例代码3: -``` +```Java String splitStr1 = "what,is,,,,"; String[] strs1 = splitStr1.split(",", -1); for (String s : strs1) { @@ -419,7 +419,7 @@ is o ``` 由于regex为',',所以满足if括号里的判断。一开始next和off指针都在0位置,limit为0,在while里的判断逻辑指的是获取','索引位置,由上图拆分的字符数组可知,next会分别为4,7,8,9,10。由于limited = limit > 0,得知limited为false,则逻辑会走到 -``` +```Java if (!limited || list.size() < limit - 1) { list.add(substring(off, next)); off = next + 1; @@ -468,7 +468,7 @@ list集合里就会添加进空字符串"" [what,is, , , ,] ``` 当程序走到时, -``` +```Java if(!limited || list.size() < limit) { list.add(substring(off, value.length); } @@ -486,7 +486,7 @@ list集合里就会添加进空字符串"" ``` 这里相信小伙伴们都知道示例1和示例2的区别在那里了,是因为示例2最后索引位置的list为空字符串,所以list.get(resultSize-1).length()为0,则会调用下面的代码逻辑: -``` +```Java while (resultSize > 0 && list.get(resultSize - 1).length() == 0) { resultSize--; } @@ -504,7 +504,7 @@ list集合里就会添加进空字符串"" 就以示例代码一为例,对于字符串"what,is,,,,"。 **对于limit > 0**,由于代码: -``` +```Java boolean limited = limit > 0; // limited为true .. .. @@ -528,7 +528,7 @@ what,is,,,, **对于limit = 0**,由于代码: -``` +```Java if (limit == 0) { while (resultSize > 0 && list.get(resultSize - 1).length() == 0) { resultSize--; @@ -543,7 +543,7 @@ is ``` **对于limit < 0**,由于代码: -``` +```Java if (!limited || list.size() < limit) list.add(substring(off, value.length)); ``` diff --git "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Thread\345\272\225\345\261\202\346\272\220\347\240\201.md" "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Thread\345\272\225\345\261\202\346\272\220\347\240\201.md" index b0b44b9..fa16533 100644 --- "a/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Thread\345\272\225\345\261\202\346\272\220\347\240\201.md" +++ "b/note/JDK/\346\267\261\345\205\245\345\255\246\344\271\240Thread\345\272\225\345\261\202\346\272\220\347\240\201.md" @@ -50,7 +50,7 @@ #### 3.1 线程优先级 优先级代表线程执行的机会的大小,优先级高的可能先执行,低的可能后执行,在 Java 源码中,优先级从低到高分别是 1 到 10,线程默认 new 出来的优先级都是 5,源码如下: -``` +```Java // 最低优先级 public final static int MIN_PRIORITY = 1; @@ -75,7 +75,7 @@ public final static int MAX_PRIORITY = 10; #### 4.1 start 启动线程 -``` +```Java // 该方法可以创建一个新的线程出来 public synchronized void start() { // 如果没有初始化,抛异常 @@ -111,7 +111,7 @@ private native void start0(); 下面只贴出部分关键源码: -``` +```Java // 无参构造器,线程名字自动生成 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); @@ -161,13 +161,13 @@ private void init(ThreadGroup g, Runnable target, String name, 当我们调用某个线程的这个方法时,这个方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。 -``` +```Java public final void join() throws InterruptedException { join(0); } ``` -``` +```Java public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); @@ -206,7 +206,7 @@ private void init(ThreadGroup g, Runnable target, String name, yield 是个 native 方法,底层代码如下: -``` +```Java public static native void yield(); ``` @@ -220,7 +220,7 @@ sleep 也是 native 方法,可以接受毫秒的一个入参,也可以接受 接受毫秒和纳秒两个入参时,如果给定纳秒大于等于 0.5 毫秒,算一个毫秒,否则不算。 -``` +```Java public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { @@ -239,7 +239,7 @@ sleep 也是 native 方法,可以接受毫秒的一个入参,也可以接受 sleep(millis); } ``` -``` +```Java public static native void sleep(long millis) throws InterruptedException; ``` @@ -252,7 +252,7 @@ interrupt 中文是打断的意思,意思是可以打断中止正在运行的 我们举一个例子来说明如何打断 WAITING 的线程,代码如下: -``` +```Java @Test public void testInterrupt() throws InterruptedException { Thread thread = new Thread(new Runnable() { diff --git "a/note/JDK/\346\267\261\345\205\245\350\247\243\350\257\273CompletableFuture\346\272\220\347\240\201\344\270\216\345\216\237\347\220\206.md" "b/note/JDK/\346\267\261\345\205\245\350\247\243\350\257\273CompletableFuture\346\272\220\347\240\201\344\270\216\345\216\237\347\220\206.md" index 5a43c54..d3271c8 100644 --- "a/note/JDK/\346\267\261\345\205\245\350\247\243\350\257\273CompletableFuture\346\272\220\347\240\201\344\270\216\345\216\237\347\220\206.md" +++ "b/note/JDK/\346\267\261\345\205\245\350\247\243\350\257\273CompletableFuture\346\272\220\347\240\201\344\270\216\345\216\237\347\220\206.md" @@ -43,7 +43,7 @@ CompletableFuture类提供了非常多的方法供我们使用,包括了runAsy **runAsync()**,异步运行, -``` +```Java @Test public void runAsyncExample() throws Exception { ExecutorService executorService = Executors.newSingleThreadExecutor(); @@ -77,7 +77,7 @@ CompletedFuture...isDown **supplyAsync()** supply有供应的意思,supplyAsync就可以理解为异步供应,查看supplyAsync()方法入参可以知道,其有两个入参: -- Supplier supplier, +- Supplier\ supplier, - Executor executor 这里先简单介绍下Supplier接口,Supplier接口是JDK8引入的新特性,它也是用于创建对象的,只不过调用Supplier的get()方法时,才会去通过构造方法去创建对象,并且每次创建出的对象都不一样。Supplier常用语法为: @@ -85,7 +85,7 @@ supply有供应的意思,supplyAsync就可以理解为异步供应,查看sup Supplier sup= MySupplier::new; ``` 再展示代码例子之前,再讲一个thenAccept()方法,可以发现thenAccept()方法的入参如下: -- Comsumer +- Comsumer\ Comsumer接口同样是java8新引入的特性,它有两个重要接口方法: 1. accept() @@ -94,7 +94,7 @@ Comsumer接口同样是java8新引入的特性,它有两个重要接口方法 thenAccept()可以理解为接收CompletableFuture的结果然后再进行处理。 下面看下supplyAsync()和thenAccept()的例子: -``` +```Java public void thenApply() throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(2); CompletableFuture cf = CompletableFuture.supplyAsync(() -> { //实现了Supplier的get()方法 @@ -132,8 +132,8 @@ public void thenApply() throws Exception { 从代码逻辑可以看出,thenApply_test等到了pool-1-thread-1线程完成任务后,才进行的调用,并且拿到了supplye()方法返回的结果,而main则异步执行了,这就避免了Future获取结果时需要阻塞或轮询的弊端。 **exceptionally** -当任务在执行过程中报错了咋办?exceptionally()方法很好的解决了这个问题,当报错时会去调用exceptionally()方法,它的入参为:Function fn,fn为执行任务报错时的回调方法,下面看看代码示例: -``` +当任务在执行过程中报错了咋办?exceptionally()方法很好的解决了这个问题,当报错时会去调用exceptionally()方法,它的入参为:Function\ fn,fn为执行任务报错时的回调方法,下面看看代码示例: +```Java public void exceptionally() { ExecutorService executorService = Executors.newSingleThreadExecutor(); CompletableFuture cf = CompletableFuture.supplyAsync(() -> { @@ -172,7 +172,7 @@ thenAcceptAsync: helloworld java.lang.RuntimeException: 测试exceptionally... CompletableFuture类实现了Future接口和CompletionStage接口,Future大家都经常遇到,但是这个CompletionStage接口就有点陌生了,这里的CompletionStage实际上是一个任务执行的一个“阶段”,CompletionStage详细的内容在下文有介绍。 -``` +```Java public class CompletableFuture implements Future, CompletionStage { volatile Object result; // CompletableFuture的结果值或者是一个异常的报装对象AltResult volatile Completion stack; // 依赖操作栈的栈顶 @@ -207,13 +207,13 @@ public class CompletableFuture implements Future, CompletionStage { runAsync()做的事情就是异步的执行任务,返回的是CompletableFuture对象,不过CompletableFuture对象不包含结果。runAsync()方法有两个重载方法,这两个重载方法的区别是Executor可以指定为自己想要使用的线程池,而runAsync(Runnable)则使用的是ForkJoinPool.commonPool()。 下面先来看看runAsync(Runnable)的源码: -``` +```Java public static CompletableFuture runAsync(Runnable runnable) { return asyncRunStage(asyncPool, runnable); } ``` 这里的asyncPool是一个静态的成员变量: -``` +```Java private static final boolean useCommonPool = (ForkJoinPool.getCommonPoolParallelism() > 1); // 并行级别 private static final Executor asyncPool = useCommonPool ? @@ -221,7 +221,7 @@ private static final Executor asyncPool = useCommonPool ? ``` 回到asyncRunStage()源码: -``` +```Java static CompletableFuture asyncRunStage(Executor e, Runnable f) { if (f == null) throw new NullPointerException(); CompletableFuture d = new CompletableFuture(); @@ -230,7 +230,7 @@ private static final Executor asyncPool = useCommonPool ? } ``` 看到asyncRunStage()源码,可以知道任务是由Executor来执行的,那么可想而知Async类一定是实现了Callable接口或者继承了Runnable类,查看Async类: -``` +```Java static final class AsyncRun extends ForkJoinTask implements Runnable, AsynchronousCompletionTask { CompletableFuture dep; Runnable fn; @@ -265,7 +265,7 @@ postComplete()的源码还是有点复杂的,先不急着分析。**先看看C #### Completion 下面先看看Completion的源码: -``` +```Java abstract static class Completion extends ForkJoinTask implements Runnable, AsynchronousCompletionTask { volatile Completion next; @@ -291,7 +291,7 @@ volatile Completion stack; ``` 由这个属性可以看出,CompletableFuture其实就是一个链表的一个数据结构。 -``` +```Java abstract static class UniCompletion extends Completion { Executor executor; // executor to use (null if none) CompletableFuture dep; // 代表的依赖的CompletableFuture @@ -322,7 +322,7 @@ abstract static class UniCompletion extends Completion { ``` claim方法要在执行action前调用,若claim方法返回false,则不能调用action,原则上要保证action只执行一次。 -``` +```Java static final class UniAccept extends UniCompletion { Consumer fn; UniAccept(Executor executor, CompletableFuture dep, @@ -342,7 +342,7 @@ static final class UniAccept extends UniCompletion { } } ``` -``` +```Java final boolean uniAccept(CompletableFuture a, Consumer f, UniAccept c) { Object r; Throwable x; @@ -370,7 +370,7 @@ final boolean uniAccept(CompletableFuture a, } ``` 对于Completion的执行,还有几个关键的属性: -``` +```Java static final int SYNC = 0;//同步 static final int ASYNC = 1;//异步 static final int NESTED = -1;//嵌套 @@ -391,14 +391,14 @@ Completion在CompletableFuture中是如何工作的呢?现在先不着急了 - Runable既不产生结果也不消耗结果 下面看看一个Stage的调用例子: -``` +```Java stage.thenApply(x -> square(x)).thenAccept(x -> System.out.println(x)).thenRun(() -> System.out.println()) ``` 这里x -> square(x)就是一个Function类型的Stage,它返回了x。x -> System.out.println(x)就是一个Comsumer类型的Stage,用于接收上一个Stage的结果x。() ->System.out.println()就是一个Runnable类型的Stage,既不消耗结果也不产生结果。 一个、两个或者任意一个CompletionStage的完成都会触发依赖的CompletionStage的执行,CompletionStage的依赖动作可以由带有then的前缀方法来实现。如果一个Stage被两个Stage的完成给触发,则这个Stage可以通过相应的Combine方法来结合它们的结果,相应的Combine方法包括:thenCombine、thenCombineAsync。但如果一个Stage是被两个Stage中的其中一个触发,则无法去combine它们的结果,因为这个Stage无法确保这个结果是那个与之依赖的Stage返回的结果。 -``` +```Java @Test public void testCombine() throws Exception { String result = CompletableFuture.supplyAsync(() -> { @@ -422,7 +422,7 @@ stage.thenApply(x -> square(x)).thenAccept(x -> System.out.println(x)).thenRun(( 下面开始介绍CompletableFuture的几个核心方法: **postComplete** -``` +```Java final void postComplete() { CompletableFuture f = this; Completion h; //this表示当前的CompletableFuture while ((h = f.stack) != null || //判断stack栈是否为空 @@ -444,7 +444,7 @@ final void postComplete() { postComplete()方法可以理解为当任务完成之后,调用的一个“后完成”方法,主要用于触发其他依赖任务。 **uniAccept** -``` +```Java final boolean uniAccept(CompletableFuture a, Consumer f, UniAccept c) { Object r; Throwable x; @@ -476,7 +476,7 @@ final boolean uniAccept(CompletableFuture a, **pushStack** -``` +```Java final void pushStack(Completion c) { do {} while (!tryPushStack(c)); //使用CAS自旋方式压入栈,避免了加锁竞争 } @@ -493,7 +493,7 @@ final boolean uniAccept(CompletableFuture a, ``` 光分析源码也没法深入理解其代码原理,下面结合一段示例代码来对代码原理进行分析。 -``` +```Java @Test public void thenApply() throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(2); @@ -533,19 +533,19 @@ final boolean uniAccept(CompletableFuture a, CompletedFuture...isDown */ -这段示例代码所做的事情就是supplyAsync(Supplier supplier)休眠200秒之后,返回一个字符串,thenAccept(Consumer action)等到任务完成之后接收这个字符串,并且调用thenApply_test()方法,随后输出 hello world。 +这段示例代码所做的事情就是supplyAsync(Supplier\ supplier)休眠200秒之后,返回一个字符串,thenAccept(Consumer\ action)等到任务完成之后接收这个字符串,并且调用thenApply_test()方法,随后输出 hello world。 代码中让线程休眠200秒是为了方便观察CompletableFuture的传递过程。 下面就描述下程序的整个运作流程。 **①** 主线程调用CompletableFuture的supplyAsync()方法,传入Supplier和Executor。在supplyAsync()中又继续调用CompletableFuture的asyncSupplyStage(Executor, Supplier)方法。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191128102326944.png) -来到asyncSupplyStage()方法中,调用指定的线程池,并执行execute(new AsyncSupply(d,f)),这里d就是我们的“源任务”,接下来thenApply()要依赖着这个源任务进行后续逻辑操作,f就是Supplier的函数式编程。 +来到asyncSupplyStage()方法中,调用指定的线程池,并执行execute(new AsyncSupply\(d,f)),这里d就是我们的“源任务”,接下来thenApply()要依赖着这个源任务进行后续逻辑操作,f就是Supplier的函数式编程。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191128102631844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) AsyncSupply实现了Runnable的run()方法,核心逻辑就在run()方法里。在run()方法里,先判断d.result == null,判断该任务是否已经完成,防止并发情况下其他线程完成此任务了。f.get()就是调用的Supplier的函数式编程,这里会休眠200秒,所以executor线程池开启的线程会在这里阻塞200秒。 **②** 虽然executor线程池线程阻塞了,但是main线程任然会继续执行接下来的代码。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191128103312134.png) -main线程会在asyncSupplyStage()方法中返回d,就是我们的“依赖任务”,而这个任务此时还处在阻塞中。接下来main线程会继续执行CompletableFuture的thenAccept(Comsumer action)方法,然后调用CompletableFuture的uniAcceptStage()方法。 +main线程会在asyncSupplyStage()方法中返回d,就是我们的“依赖任务”,而这个任务此时还处在阻塞中。接下来main线程会继续执行CompletableFuture的thenAccept(Comsumer\ action)方法,然后调用CompletableFuture的uniAcceptStage()方法。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019112810354686.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 在uniAcceptStage()方法中,会将“依赖任务”、“源任务”、线程池以及Comsumer报装程一个UniAccept对象,然后调用push()压入stack的栈顶中。随后调用UniAccept的tryFire()方法。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191128103848372.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) @@ -555,7 +555,7 @@ main线程会在asyncSupplyStage()方法中返回d,就是我们的“依赖任 **③** 回到“源任务”,虽然main线程已经结束了整个生命周期,但是executor线程池的线程任然阻塞着的,休眠了200秒之后,继续执行任务。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20191128105600904.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) -然后来到了postComplete()方法。这个方法在前面已经介绍到了,它是CompletableFuture的核心方法之一,做了许多事情。最重要的一件事情就是触发其他依赖任务,接下来调用的方法依次为:UniAccept.tryFire(mode) ——> CompletableFuture.uniAccept(..) ——> Comsumer.accept(s) ——> 输出“hello world”,并输出当前调用线程的线程名。因这个调用链已经在②中介绍过了,所以就不再详细介绍其运作逻辑。 +然后来到了postComplete()方法。这个方法在前面已经介绍到了,它是CompletableFuture的核心方法之一,做了许多事情。最重要的一件事情就是触发其他依赖任务,接下来调用的方法依次为:UniAccept.tryFire(mode) ——\> CompletableFuture.uniAccept(..) ——\> Comsumer.accept(s) ——\> 输出“hello world”,并输出当前调用线程的线程名。因这个调用链已经在②中介绍过了,所以就不再详细介绍其运作逻辑。 **小结:** 通过这个小示例,终于理解到了“源任务”和“依赖任务”之间的调用关系,以及CompletableFuture的基本运作原理。然而CompletableFuture还有其他的方法需要去深入分析,由于篇幅所限就不再赘述,感兴趣的读者可以以debug的模式去一点一点分析CompletableFuture其他方法的底层原理。这里不得不说Java并发包作者Doug Lea大神真的太厉害了,阅读他的源码之后,可以发现他写的代码不能以技术来形容,而应该使用“艺术”来形容。 diff --git "a/note/Netty/IO\345\272\225\345\261\202\345\216\237\347\220\206.md" "b/note/Netty/IO\345\272\225\345\261\202\345\216\237\347\220\206.md" new file mode 100644 index 0000000..b66a558 --- /dev/null +++ "b/note/Netty/IO\345\272\225\345\261\202\345\216\237\347\220\206.md" @@ -0,0 +1,66 @@ +## 从linux kernel内核出发,IO底层原理 + +### 1. BIO + +``` +import java.io.InputStream; +import java.net.ServerSocket; +import java.net.Socket; + +/** + * @author lhy + * + * 在windows服务器下,可以使用telnet来合serversocket建立连接 + */ +public class BIO { + public static void main(String[] args) throws Exception { + ServerSocket serverSocket = new ServerSocket(666); + System.out.println("Server started..."); + while (true) { + System.out.println("socket accepting..."); + Socket socket = serverSocket.accept(); + new Thread(new Runnable() { + @Override + public void run() { + try { + byte[] bytes = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + while (true) { + System.out.println("reading..."); + int read = inputStream.read(bytes); + if (read != -1) { + System.out.println(new String(bytes, 0, read)); + } else { + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }).start(); + } + } +} +``` + +#### 1.1 从kernel内核的角度来分析BIO的运行机制 + +### 2. IO多路复用 + +#### 2.1 select + +#### 2.2 poll + +#### 2.3 epoll + +### 3. 零拷贝 + + + diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\212\357\274\211.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\212\357\274\211.md" new file mode 100644 index 0000000..706e68c --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\212\357\274\211.md" @@ -0,0 +1,848 @@ +## 1. 概览 + +在Netty中,ChannelPipeline和ChannelHandler类似于Servlet和Filter过滤器,这类拦截器实际上是责任链模式的一种变形,这么设计是为了方便事件的拦截和用户业务逻辑的定制。 + +Servlet和Filter是JAVAEE中的基础组件,初学JAVAEE的小伙伴再熟悉不过了,它能够拦截到HTTP请求以及响应,并读出请求和响应的数据用作业务逻辑的处理,通过这种方式Servlet和Filter可以对Web应用程序进行预处理和后置处理。 + +Netty的Channel过滤器实现原理与ServletFilter机制一致,它将Channel的数据管道抽象为ChannelPipeline, 消息在ChannelPipeline中流动和传递。ChannelPipeline 持有I/O事件拦截器ChannelHandler的链表,由ChanneIHandler对I/O事件进行拦截和处理,可以方便地通过新增和删除ChannelHandler来实现不同的业务逻辑定制,不需要对已有的ChannelHandler进行修改,能够实现对修改封闭和对扩展的支持。总的而言,在Netty中,pipeline相当于Netty的大动脉,负责Netty的读写事件的传播。 + +## 2. ChannelPipeline原理分析 + +ChannelPipeline是ChannelHandler的容器,它负责ChannelHandler的管理和事件拦截。下面用一张图来展示在Netty中,一个“消息”被ChannelPipeline拦截,然后被ChannelHandler处理的过程,流程如下: + +![ChannelPipeline01](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline01.png) + +1) 底层的SocketChannel read(方法读取ByteBuf, 触发ChannelRead 事件,由I/O线程NioEventLoop 调用ChannelPipeline 的fireChannelRead(Object msg)方法, 将消息 +(ByteBuf)传输到ChannelPipeline中 +2) 消息依次被HeadHandler、ChannelHandler1、 ChannelHander2.....TailHandler 拦截和处理,在这个过程中,任何ChannelHandler都可以中断当前的流程,结束消息的传递 +3) 调用ChannelHandlerContext的write方法发送消息,消息从TailHandler开始,途经ChannelHanderN.....ChannelHandlerl. HeadHandler, 最终被添加到消息发送缓冲区中等待刷新和发送,在此过程中也可以中断消息的传递,例如当编码失败时,就需要中断流程,构造异常的Future返回 + + +Netty中的事件分为inbound事件和outbound事件.inbound事件通常由1/O线程触发,例如TCP链路建立事件、链路关闭事件、读事件、异常通知事件等,它对应上图的左半部分。 + +触发inbound事件的方法如下: + +1) ChannelHandlerContext#fireChannelRegistered(): Channel 注册事件 +2) ChannelHandlerContext#fireChannelActive(): TCP链路建立成功, Channel激活事件 +3) ChannelHandlerContext#fireChannelRead(Object): 读事件 +4) ChannelHandlerContext#fireChannelReadComplete(): 读操作完成通知事件; +5) ChannelHandlerContext#fireExceptionCaught(Throwable): 异常通知事件; +6) ChannelHandlerContext#fireUserEventTriggered(Object): 用户自定义事件: +7) ChannelHandlerContext#fireChannelWritabilityChanged(): Channel 的可写状态变化通知事件; +8) ChannelHandlerContext#fireChannellnactive(): TCP连接关闭,链路不可用通知事件。 + +Outbound事件通常是由用户主动发起的网络I/O操作,例如用户发起的连接操作、绑定操作、消息发送等操作,它对应上图的右半部分。 + +触发outbound事件的方法如下: + +1) ChannelHandlerContext#bind( SocketAddress, ChannelPromise):绑定本地地址事件 +2) ChannelHandlerContext#connect(SocketAddress, SocketAddress, ChannelPromise):连接服务端事件 +3) ChannelHandlerContext#write(Object, ChannelPromise):发送事件 +4) ChannelHandlerContext#flushO): 刷新事件 +5) ChannelHandlerContext#read(): 读事件 +6) ChannelHandlerContext#disconnect(ChannelPromise): 断开连接事件 +7) ChannelHandlerContext#close(ChannelPromise): 关闭当前Channel事件 + + +### 2.1 ChannelPipeline接口 + +为了接下来能够方便的学习原理以及阅读源码,我们先看下ChannelPipeline的接口的继承关系图: + +!["ChannelPipeline02"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline02.png) + +可以发现,ChannelPipeline接口还继承了ChannelOutboundInvoker以及ChannelInboundInvoker,这两个invoker接口作为ChannelPipeline接口的增强接口。分别看下ChannelPipeline和ChannelOutboundInvoker、ChannelInboundInvoker这三个接口定义。 + +对于ChannelPipeline接口,方法分别可以分为以下几组类别方法: + +第一组是向ChannelPipeline中添加ChannelHandler,如下图所示: + +!["ChannelPipeline03_01"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline03_01.png) + +> 这里需要提前知道的是,ChannelPipeline维护这一组双向链表的数据结构。 + +addFirst是向ChannelPipeline双向链表头补添加节点,addLast是向ChannelPipeline双向链表尾部添加节点,addBefore是向ChannelPipeline双向链表中指定的ChannelHandler之前添加一个新的节点,addAfter是向ChannelPipeline双向链表中指定的ChannelHandler之后添加一个节点。 + + +第二组是向ChannelPipeline中移除ChannelHandler + +!["ChannelPipeline03_02"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline03_02.png) + + + +第三组是向获取ChannelHandlerContext对象 + +!["ChannelPipeline03_03"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline03_03.png) + + +第四组是ChannelInboundInvoker接口增强而来的方法 +!["ChannelPipeline03_04"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline03_04.png) + + + +第五组是ChannelOutboundInvoker接口增强而来的方法 +!["ChannelPipeline03_05"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline03_05.png) + + +在Netty中,ChannelPipeline是一个双向链表的数据结构,那么链表节点是什么呢?答案就是ChannelHandlerContext对象。 + +在Netty中,ChannelHandlerContext对象就是存在ChannelPipeline双向链表中的节点元素,在ChannelPipeline中,Netty会为其初始化Head头结点和Tail尾结点,在ChannelPipeline实现类:DefaultChannelPipeline中可以看到定义: + +```java + final AbstractChannelHandlerContext head; + final AbstractChannelHandlerContext tail; +``` + +DefaultChannelPipeline构造方法中,对head和tail进行了初始化 + +```java + protected DefaultChannelPipeline(Channel channel) { + // 给channel赋值channel对象 + this.channel = ObjectUtil.checkNotNull(channel, "channel"); + succeededFuture = new SucceededChannelFuture(channel, null); + voidPromise = new VoidChannelPromise(channel, true); + + // 节点对象是AbstractChannelHandlerContext对象,是用于进行业务处理 + // HeadContext和TailContext就是用户可以模仿实现的ChannelHandler实现类 + // channelPipeline双向连表的头节点 + tail = new TailContext(this); + // channelPipeline双向连表的尾结点 + head = new HeadContext(this); + + // channelPipeline: head -> tail + head.next = tail; + tail.prev = head; + } +``` + +当Netty初始化完DefaultChannelPipeline对象之后,ChannelPipeline中就已经存在了head和tail两个节点了,自然Netty会通过前面介绍的addXxx方法来添加,下面看下ChannelPipeline的addXxx方法源代码: + + +DefaultChannelPipeline.java +```java + @Override + public final ChannelPipeline addLast(String name, ChannelHandler handler) { + return addLast(null, name, handler); + } + + @Override + public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) { + final AbstractChannelHandlerContext newCtx; + synchronized (this) { + checkMultiplicity(handler); + + /** + * 生成一个新的ChannelHandlerContext对象,这里返回的是DefaultChannelHandlerContext对象 + */ + newCtx = newContext(group, filterName(name, handler), handler); + + /** + * 向pipeline链表中添加一个新的节点 + */ + addLast0(newCtx); + + // If the registered is false it means that the channel was not registered on an eventLoop yet. + // In this case we add the context to the pipeline and add a task that will call + // ChannelHandler.handlerAdded(...) once the channel is registered. + if (!registered) { + newCtx.setAddPending(); + // 触发handlerAdded方法,并开始传播handlerAdded事件,此处最终会调用ChannelInitializer#handlerAdded方法,并最终调用到initChannel方法。 + callHandlerCallbackLater(newCtx, true); + return this; + } + + /** + * 从NioEventLoopGroup中获取到NioEventLoop对象 + */ + EventExecutor executor = newCtx.executor(); + if (!executor.inEventLoop()) { + callHandlerAddedInEventLoop(newCtx, executor); + return this; + } + } + // 调用HandlerAdded方法 + callHandlerAdded0(newCtx); + return this; + } + + // 向尾结点添加一个节点,并移动指针位置 + private void addLast0(AbstractChannelHandlerContext newCtx) { + AbstractChannelHandlerContext prev = tail.prev; + newCtx.prev = prev; + newCtx.next = tail; + prev.next = newCtx; + tail.prev = newCtx; + } + + private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) { + return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler); + } +``` + +那么,对于拥有双向链表结构的ChannelPipeline来说,是如何让事件在链表结构中进行转移执行的? + +就拿fireChannelRead方法来分析: + +这里需要提前知道的一点是,AbstractCHannelHandlerContext#fireChannelRead方法会被复写了channelRead方法的ChannelHandler调用。 + +AbstractCHannelHandlerContext.java +```java + @Override + public ChannelHandlerContext fireChannelRead(final Object msg) { + /** + * findContextInbound 返回的-> AbstractChannelHandlerContext对象 + */ + invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg); + return this; + } + + /** + * 查找下一个Inbound节点 + * @param mask + * @return + */ + private AbstractChannelHandlerContext findContextInbound(int mask) { + AbstractChannelHandlerContext ctx = this; + EventExecutor currentExecutor = executor(); + do { + ctx = ctx.next; + } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND)); + return ctx; + } + + private static boolean skipContext( + AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) { + // Ensure we correctly handle MASK_EXCEPTION_CAUGHT which is not included in the MASK_EXCEPTION_CAUGHT + return (ctx.executionMask & (onlyMask | mask)) == 0 || + // We can only skip if the EventExecutor is the same as otherwise we need to ensure we offload + // everything to preserve ordering. + // + // See https://github.com/netty/netty/issues/10067 + (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0); + } +``` + +经过while循环遍历出下一个节点之后,变调用DefaultChannelPipeline#invokeChannelRead方法。 + +DefaultChannelPipeline +```java + static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { + final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); + EventExecutor executor = next.executor(); + // 在Netty线程中,则直接调用 + if (executor.inEventLoop()) { + next.invokeChannelRead(m); + } else { + // 不在Netty线程中,则另开一个线程来调用ChanelRead方法 + executor.execute(new Runnable() { + @Override + public void run() { + next.invokeChannelRead(m); + } + }); + } + } + + private void invokeChannelRead(Object msg) { + // 过滤handler的状态 + if (invokeHandler()) { + try { + // 调用inboundHandler的channelRead方法 + ((ChannelInboundHandler) handler()).channelRead(this, msg); + } catch (Throwable t) { + invokeExceptionCaught(t); + } + } else { + fireChannelRead(msg); + } + } +``` + +就这样,前一个节点的ChannelRead方法执行完,就会遍历出下一个节点的ChannelRead并执行,以此达到了在双向链表中移动节点元素的效果。 + +### 2.2 ChannelPipeline对象是在哪里创建的 + +Netty中,ChannelPipeline对象是在Channel被创建的时候生成的,看源码。 + +AbstractChannel.java +```java + protected AbstractChannel(Channel parent) { + this.parent = parent; + // channel的标识 + id = newId(); + // channel的unsafe类 + // NioSocketChannel和NioServerSocketChannel的unsafe对象都一样 + unsafe = newUnsafe(); + // 新建pipeline + pipeline = newChannelPipeline(); + } + + protected DefaultChannelPipeline newChannelPipeline() { + // 随后调用DefaultChannelPipeline对象构造方法,在构造方法中生成TailContext和HeadContext,并维护好他们的链表关系 + return new DefaultChannelPipeline(this); + } +``` + +## 3. ChannelHandler原理分析 + +ChannelPipeline是通过ChannelHandler接口来实现事件的拦截和处理,一般ChannelHandler只需要继承ChannelHandlerAdapter,然后覆盖自己关心的方法即可。 + +对于ChannelHandler接口,先看下其接口实现图: + +!["netty_pipeline01"](https://coderbruis.github.io/javaDocs/img/netty/source/netty_pipeline01.png) + +可以看到ChannelHandler接口的子类实现图中,有两个重要的子接口:ChannelInboundHandler、ChannelOutboundHandlerAdapter,这两个子接口扩展了ChannelHandler的功能,分别对应着ChannelPipeline章节中介绍的inbound和outbound事件功能。先看看ChannelHandler接口定义了哪些方法。 + +### 3.1 ChannelHandler接口 + +```java +public interface ChannelHandler { + + /** + * 添加ChannelHandler的回调 + */ + void handlerAdded(ChannelHandlerContext ctx) throws Exception; + + /** + * 移除ChannelHandler的回调 + */ + void handlerRemoved(ChannelHandlerContext ctx) throws Exception; + + /** + * + */ + @Deprecated + void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; + + @Inherited + @Documented + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface Sharable { + // no value + } +} + +``` + +ChannelHandler是作为业务处理器保存在ChannelPipeline中的,它的其他功能都是在子类实现或者是子接口继承的,下面看下: +ChannelHandlerAdapter + +```java +public abstract class ChannelHandlerAdapter implements ChannelHandler { + /** + * 判断当前这个ChannelHandler是否有@Shareble修饰,有的话该ChannelHandler就可以在不同的ChannelPipeline之间共享 + */ + public boolean isSharable() { + /** + * Cache the result of {@link Sharable} annotation detection to workaround a condition. We use a + * {@link ThreadLocal} and {@link WeakHashMap} to eliminate the volatile write/reads. Using different + * {@link WeakHashMap} instances per {@link Thread} is good enough for us and the number of + * {@link Thread}s are quite limited anyway. + * + * See #2289. + */ + Class clazz = getClass(); + Map, Boolean> cache = InternalThreadLocalMap.get().handlerSharableCache(); + Boolean sharable = cache.get(clazz); + if (sharable == null) { + sharable = clazz.isAnnotationPresent(Sharable.class); + cache.put(clazz, sharable); + } + return sharable; + } + + @Skip + @Override + @Deprecated + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + ctx.fireExceptionCaught(cause); + } + +} +``` + +可以看到,ChannelHandlerAdapter作为抽象类只实现了顶级接口ChannelHandler的两个方法:isShareble和exceptionCaught,这里是Netty的风格之一,就是定义完顶级接口后,分别有公共抽象子类、子接口来对功能进行增强。那么对于ChannelHandler的功能增强,则由:ChannelOutboundHandler、ChannelInboundHandler来进行的增强。 + +### 3.2 ChannelOutboundHandler、ChannelInboundHandler接口 + +```java +public interface ChannelInboundHandler extends ChannelHandler { + + /** + * 通道注册完成的回调方法,方法中多以fireChannelRegistered方法为主,作用是往pipeline中传播channelRegistered事件 + */ + void channelRegistered(ChannelHandlerContext ctx) throws Exception; + + /** + * 通道解除注册的回调方法 + */ + void channelUnregistered(ChannelHandlerContext ctx) throws Exception; + + /** + * 通道触发 + */ + void channelActive(ChannelHandlerContext ctx) throws Exception; + + /** + * The {@link Channel} of the {@link ChannelHandlerContext} was registered is now inactive and reached its + * end of lifetime. + */ + void channelInactive(ChannelHandlerContext ctx) throws Exception; + + /** + * 通道读取到消息 + */ + void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; + + /** + * Invoked when the last message read by the current read operation has been consumed by + * {@link #channelRead(ChannelHandlerContext, Object)}. If {@link ChannelOption#AUTO_READ} is off, no further + * attempt to read an inbound data from the current {@link Channel} will be made until + * {@link ChannelHandlerContext#read()} is called. + */ + void channelReadComplete(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called if an user event was triggered. + */ + void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception; + + /** + * Gets called once the writable state of a {@link Channel} changed. You can check the state with + * {@link Channel#isWritable()}. + */ + void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception; + + /** + * Gets called if a {@link Throwable} was thrown. + */ + @Override + @SuppressWarnings("deprecation") + void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; +} +``` + +```java +public interface ChannelOutboundHandler extends ChannelHandler { + /** + * 绑定socket事件回调 + */ + void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception; + + /** + * socket连接回调 + */ + void connect( + ChannelHandlerContext ctx, SocketAddress remoteAddress, + SocketAddress localAddress, ChannelPromise promise) throws Exception; + + /** + * socket断开连接回调 + */ + void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * socket关闭回调 + */ + void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * Called once a deregister operation is made from the current registered {@link EventLoop}. + * + * @param ctx the {@link ChannelHandlerContext} for which the close operation is made + * @param promise the {@link ChannelPromise} to notify once the operation completes + * @throws Exception thrown if an error occurs + */ + void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception; + + /** + * Intercepts {@link ChannelHandlerContext#read()}. + */ + void read(ChannelHandlerContext ctx) throws Exception; + + /** + * + */ + void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception; + + /** + * + */ + void flush(ChannelHandlerContext ctx) throws Exception; +} +``` + +### 3.3 ChannelInitializer抽象类 + +在来看看ChannelInitializer这个抽象类,定义了什么功能。 + +```java +@Sharable +public abstract class ChannelInitializer extends ChannelInboundHandlerAdapter { + + private static final InternalLogger logger = InternalLoggerFactory.getInstance(ChannelInitializer.class); + // initMap用于保存在不同pipeline之间共享的ChannelHandler对象,减少开销 + private final Set initMap = Collections.newSetFromMap( + new ConcurrentHashMap()); + + /** + * 初始化channel的抽象方法,具体由子类提供实现逻辑 + */ + protected abstract void initChannel(C ch) throws Exception; + + @Override + @SuppressWarnings("unchecked") + public final void channelRegistered(ChannelHandlerContext ctx) throws Exception { + // 通道注册完成后,对通道进行初始化 + if (initChannel(ctx)) { + // 将通道注册完这个事件往pipeline里传播 + ctx.pipeline().fireChannelRegistered(); + + // We are done with init the Channel, removing all the state for the Channel now. + removeState(ctx); + } else { + ctx.fireChannelRegistered(); + } + } + + /** + * Handle the {@link Throwable} by logging and closing the {@link Channel}. Sub-classes may override this. + */ + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (logger.isWarnEnabled()) { + logger.warn("Failed to initialize a channel. Closing: " + ctx.channel(), cause); + } + ctx.close(); + } + + /** + * {@inheritDoc} If override this method ensure you call super! + */ + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + if (ctx.channel().isRegistered()) { + // This should always be true with our current DefaultChannelPipeline implementation. + // The good thing about calling initChannel(...) in handlerAdded(...) is that there will be no ordering + // surprises if a ChannelInitializer will add another ChannelInitializer. This is as all handlers + // will be added in the expected order. + if (initChannel(ctx)) { + + // We are done with init the Channel, removing the initializer now. + removeState(ctx); + } + } + } + + @Override + public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { + initMap.remove(ctx); + } + + @SuppressWarnings("unchecked") + private boolean initChannel(ChannelHandlerContext ctx) throws Exception { + if (initMap.add(ctx)) { // Guard against re-entrance. + try { + // 调用抽象方法initChannel(channel) + initChannel((C) ctx.channel()); + } catch (Throwable cause) { + // Explicitly call exceptionCaught(...) as we removed the handler before calling initChannel(...). + // We do so to prevent multiple calls to initChannel(...). + exceptionCaught(ctx, cause); + } finally { + ChannelPipeline pipeline = ctx.pipeline(); + if (pipeline.context(this) != null) { + pipeline.remove(this); + } + } + return true; + } + return false; + } + + // 将Channelhandler从initMap中移除 + private void removeState(final ChannelHandlerContext ctx) { + // The removal may happen in an async fashion if the EventExecutor we use does something funky. + if (ctx.isRemoved()) { + initMap.remove(ctx); + } else { + // The context is not removed yet which is most likely the case because a custom EventExecutor is used. + // Let's schedule it on the EventExecutor to give it some more time to be completed in case it is offloaded. + ctx.executor().execute(new Runnable() { + @Override + public void run() { + initMap.remove(ctx); + } + }); + } + } +} +``` + +对于ChannelInboundHandlerAdapter这个抽象类来说,已经实现了ChannelInboundHandler这个接口的所有方法了,而ChannelOutboundHandlerAdapter抽象类同样已经实现了ChannelOutboundHandler接口的所有方法,因此继承了ChannelInitializer的实现类,只需要实现initChannel(Channel ch)方法即可。 + +下面看一个ChannelInitializer的例子 + +```java +public class HttpHelloWorldServerInitializer extends ChannelInitializer { + + private final SslContext sslCtx; + + public HttpHelloWorldServerInitializer(SslContext sslCtx) { + this.sslCtx = sslCtx; + } + + @Override + public void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + if (sslCtx != null) { + p.addLast(sslCtx.newHandler(ch.alloc())); + } + p.addLast(new HttpServerCodec()); + p.addLast(new HttpServerExpectContinueHandler()); + p.addLast(new HttpHelloWorldServerHandler()); + } +} + +``` + +由上面的例子知道,对于initChannel(Channel ch)方法而言,主要是用于往pipeline中添加ChannelHandler的。 + +## 4. ChannelHandlerContext源码分析 + +ChannelHandlerContext接口定义 + +```java +public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker { + + /** + * 获得一个channel对象 + */ + Channel channel(); + + /** + * 获取一个EventExecutor对象,这里实际获得的是NioEventLoop + */ + EventExecutor executor(); + + /** + * 获取ChannelHandler的名称 + */ + String name(); + + /** + * 绑定在ChannelHandlerContext上的ChannelHandler + */ + ChannelHandler handler(); + + boolean isRemoved(); + + @Override + ChannelHandlerContext fireChannelRegistered(); + + @Override + ChannelHandlerContext fireChannelUnregistered(); + + @Override + ChannelHandlerContext fireChannelActive(); + + @Override + ChannelHandlerContext fireChannelInactive(); + + @Override + ChannelHandlerContext fireExceptionCaught(Throwable cause); + + @Override + ChannelHandlerContext fireUserEventTriggered(Object evt); + + @Override + ChannelHandlerContext fireChannelRead(Object msg); + + @Override + ChannelHandlerContext fireChannelReadComplete(); + + @Override + ChannelHandlerContext fireChannelWritabilityChanged(); + + @Override + ChannelHandlerContext read(); + + @Override + ChannelHandlerContext flush(); + + /** + * 获取pipeline + */ + ChannelPipeline pipeline(); + + /** + * 获取一个内存分配器 + */ + ByteBufAllocator alloc(); + + /** + * @deprecated Use {@link Channel#attr(AttributeKey)} + */ + @Deprecated + @Override + Attribute attr(AttributeKey key); + + /** + * @deprecated Use {@link Channel#hasAttr(AttributeKey)} + */ + @Deprecated + @Override + boolean hasAttr(AttributeKey key); +} +``` + + +可以看到ChannelHandlerContext分别继承了ChannelInboundInvoker和ChannelOutboundInvoker接口,在分析Channelpipeline章节时,介绍过其二者定义的功能,ChannelInboundInvoker多以fireXxxx方法构成,代表的是触发的Xxx事件的传播,例如: + +```java + @Override + public ChannelHandlerContext fireChannelRegistered() { + invokeChannelRegistered(findContextInbound(MASK_CHANNEL_REGISTERED)); + return this; + } +``` + +fireChannelRegistered方法就是触发了ChannelRegistered事件能够在ChannelPipeline中进行传播。 + +而ChannelOutboundInvoker多以socket的绑定、连接、读和写为住,常见的方法由write、flush以及writeAndFlush。 + +```java + @Override + public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) { + write(msg, true, promise); + return promise; + } +``` + +按照Netty架构的习惯,在给定一个接口之后,一般都会有对应的公共抽象类来定义公共的方法,并将需要定制的方法定义为抽象方法供不同的子类实现,照着这个思路可以找到AbstractChannelHandlerContext这个抽象类。 + +### 4.1 AbstractChannelHandlerContext源码分析 + +> 成员变量定义 + +在分析AbstractChannelHandlerContext源码之前,我们先看下它的成员变量定义,入下图所示,定义了两个volatile对象: + +1. volatile AbstractChannelHandlerContext next +2. volatile AbstractChannelHandlerContext prev + +这两个AbstractChannelHandlerContext对象作为指针实现了ChannelPipeline作为双向链表的数据结构。 + +```java + private static final AtomicIntegerFieldUpdater HANDLER_STATE_UPDATER = + AtomicIntegerFieldUpdater.newUpdater(AbstractChannelHandlerContext.class, "handlerState"); +``` + +> 关于XxxFieldUpdater类,可以阅读:https://my.oschina.net/u/4072299/blog/3115164 + +接着是AbstractChannelHandlerContext的状态: + +- private static final int ADD_PENDING = 1 +- private static final int ADD_COMPLETE = 2 +- private static final int REMOVE_COMPLETE = 3 + +ADD_PENDING表示正在调用handlerAdded,ADD_COMPLETE表示已经调用完成了handlerAdded,而REMOVE_COMPLETE表示已经调用完handlerRemoved方法。 + +而ChannelHandlerContext中还会存有ChannelPipeline。 + +```java +private final DefaultChannelPipeline pipeline +``` + +还有一个handlerState变量,用于定义当前ChannelHandler对象的状态,初始为INIT状态,表示handlerAdded和handlerRemove都还未调用过。 + +```java +private volatile int handlerState = INIT +``` + +!["ChannelPipeline-abstractChannelHandlerContext01"](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline-abstractChannelHandlerContext01.png) + + +对于ChannelPipeline中的ChannelHandler是如何被调用以及如何移动双向链表中的对象的,实现原理就在这几个方法之间: + +AbstractChannelHandlerContext.java +```java + + /* + * 触发ChannelActive事件 + */ + @Override + public ChannelHandlerContext fireChannelActive() { + invokeChannelActive(findContextInbound(MASK_CHANNEL_ACTIVE)); + return this; + } + + static void invokeChannelActive(final AbstractChannelHandlerContext next) { + // 获取NioEventLoop线程 + EventExecutor executor = next.executor(); + // 如果获取到的NioEventLoop线程是当前的线程 + if (executor.inEventLoop()) { + next.invokeChannelActive(); + } else { + // 另开一个线程去执行 + executor.execute(new Runnable() { + @Override + public void run() { + next.invokeChannelActive(); + } + }); + } + } + + private void invokeChannelActive() { + // 检查ChannelHandler的状态 + if (invokeHandler()) { + try { + ((ChannelInboundHandler) handler()).channelActive(this); + } catch (Throwable t) { + invokeExceptionCaught(t); + } + } else { + // 遍历下一个节点,重新调用下一个节点 + fireChannelActive(); + } + } + + /* + * 找到inbound节点 + */ + private AbstractChannelHandlerContext findContextInbound(int mask) { + AbstractChannelHandlerContext ctx = this; + // 拿到当前的NioEventLoop线程 + EventExecutor currentExecutor = executor(); + do { + // 获取ChannelPipeline中的next节点 + ctx = ctx.next; + } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND)); + return ctx; + } + + // 判断是否跳过此节点 + private static boolean skipContext( + AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) { + return (ctx.executionMask & (onlyMask | mask)) == 0 || + (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0); + } +``` + +当调用来到 +```java +((ChannelInboundHandler) handler()).channelActive(this); +``` +会触发下一个Handler的channelActive,此处就拿Tail节点的channelActive来分析。 + +```java + @Override + public void channelActive(ChannelHandlerContext ctx) { + ctx.fireChannelActive(); + readIfIsAutoRead(); + } +``` +可以看到,调用又来到了ChannelHandlerContext#fireChannelActive,这样又要进行节点的遍历,就这样把事件传播了下去。 + + +由于篇幅原因,剩余ChannelPipeline的分析放在下一篇进行。 \ No newline at end of file diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\213\357\274\211.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\213\357\274\211.md" new file mode 100644 index 0000000..9683989 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-ChannelPipeline\345\210\206\346\236\220\357\274\210\344\270\213\357\274\211.md" @@ -0,0 +1,322 @@ +## 1. 概览 + +上篇已经讲解了ChannelPipeline以及ChannelHandler的关系以及对应的类继承关系图,本节来详细分析一下inbound和outbound的原理。 + +## 2. DefaultChannelPipeline源码分析 + +在DefaultChannelPipeline中,定义了一个head“头结点”和一个tail“尾结点”,它们都是AbstractChannelhandlerContext类的节点,我们都知道在ChannelPipeline中AbstractChannelHandlerContext就是节点元素的抽象类实现,而这个handlerContext持有ChannelHandler。 + +在Netty中我们还需要知道inbound和outbound类型的ChannelHandler节点的执行顺序。 + +下面来先看下一个Netty的demo + +该Netty的demo中,分别定义了六个Handler,分为两组,一组是inboundHandler,另一组是outboundHandler。 + + +InBoundHandlerA +```java +public class InBoundHandlerA extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerA: " + msg); + ctx.fireChannelRead(msg); + } +} +``` + +InBoundHandlerB +```java +public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter { + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerB: " + msg); + ctx.write(msg, promise); + } + + + @Override + public void handlerAdded(final ChannelHandlerContext ctx) { + ctx.executor().schedule(() -> { + ctx.channel().write("ctx.channel().write -> hello world"); + ctx.write("hello world"); + }, 3, TimeUnit.SECONDS); + } +} +``` + +InBoundHandlerC +```java +public class InBoundHandlerC extends ChannelInboundHandlerAdapter { + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerC: " + msg); + ctx.fireChannelRead(msg); + } +} +``` + +```java +public final class Server { + + public static void main(String[] args) throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.TCP_NODELAY, true) + .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new InBoundHandlerA()); + ch.pipeline().addLast(new InBoundHandlerB()); + ch.pipeline().addLast(new InBoundHandlerC()); + } + }); + + ChannelFuture f = b.bind(8888).sync(); + + f.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} +``` + +执行结果如下: +``` +InBoundHandlerA: hello world +InBoundHandlerB: hello world +InBoundHandlerC: hello world +``` + +可以发现Netty中,对于inboundHandler来说是按照顺序执行操作的。 + +接着在看看outboundHandler定义如下 + +OutBoundHandlerA +```java +public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerA: " + msg); + ctx.write(msg, promise); + } +} +``` + +OutBoundHandlerB +```java +public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter { + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerB: " + msg); + ctx.write(msg, promise); + } +} +``` + +OutBoundHandlerC +```java +public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter { + + @Override + public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { + System.out.println("OutBoundHandlerC: " + msg); + ctx.write(msg, promise); + } +} +``` + + +然后修改Server类为如下, + +```java +public final class Server { + + public static void main(String[] args) throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.TCP_NODELAY, true) + .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new OutBoundHandlerA()); + ch.pipeline().addLast(new OutBoundHandlerB()); + ch.pipeline().addLast(new OutBoundHandlerC()); + } + }); + + ChannelFuture f = b.bind(8888).sync(); + + f.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} +``` + +执行结果如下: +``` +OutBoundHandlerC: ctx.channel().write -> hello world +OutBoundHandlerB: ctx.channel().write -> hello world +OutBoundHandlerA: ctx.channel().write -> hello world +OutBoundHandlerA: hello world +``` + +可以看到在Netty中对于ountboundHandler来说,是倒序执行的。 + +整个Netty执行ChannelHandler可以用下图来描述。 + +![channelPipeline事件传播图](https://coderbruis.github.io/javaDocs/img/netty/source/ChannelPipeline事件传播图.png) + + +上图描述的Head节点顺序执行,Tail节点逆序执行的源码是在DefaultChannelPipeline中,在《Netty-ChannelPipeline-上》文章开头就已经说明了,对于inboundHandler类型的Handler,主要还是用于监听Channel的read、register、active、exceptionCaught等事件,而对于outboundHandler类型来说,主要是用于bind、connect、write、flush等事件,回顾了这一点后,我们在继续看DefaultChannelPipeline源码 + +```java +public class DefaultChannelPipeline implements ChannelPipeline { + ... 省略 + + @Override + public final ChannelPipeline fireChannelRead(Object msg) { + AbstractChannelHandlerContext.invokeChannelRead(head, msg); + return this; + } + + @Override + public final ChannelFuture write(Object msg) { + return tail.write(msg); + } + + ... 省略 +} +``` + +分别以inbound类型的channelRead和outbound类型的write来分析。 + +DefaultChannelPipeline.java +```java + @Override + public final ChannelPipeline fireChannelRead(Object msg) { + AbstractChannelHandlerContext.invokeChannelRead(head, msg); + return this; + } +``` +在AbstractChannelHandlerContext#invokeChannelRead方法中,传入了一个重要的入参:head,这里就是传入的Head头结点,这一重要调用得以让inbound类型handler在ChannelPipeline中按顺序执行。 + +AbstractChannelHandlerContext.java +```java + static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { + final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); + EventExecutor executor = next.executor(); + // 在NioEventLoop线程内,next这里传入的是head头结点 + if (executor.inEventLoop()) { + next.invokeChannelRead(m); + } else { + executor.execute(new Runnable() { + @Override + public void run() { + next.invokeChannelRead(m); + } + }); + } + } + + private void invokeChannelRead(Object msg) { + if (invokeHandler()) { + try { + ((ChannelInboundHandler) handler()).channelRead(this, msg); + } catch (Throwable t) { + invokeExceptionCaught(t); + } + } else { + fireChannelRead(msg); + } + } + +``` + +ChannelInboundHandler#channelRead的调用,会最终来到InBoundHandlerA里的channelRead方法。 +```java +public class InBoundHandlerA extends ChannelInboundHandlerAdapter { + + @Override + public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("InBoundHandlerA: " + msg); + ctx.fireChannelRead(msg); + } +} +``` + +经过AbstractChannelHandlerContext#fireChannelRead,会在ChannelPipeline中寻找下一个inbound,然后继续执行channelRead。 + +```java + @Override + public ChannelHandlerContext fireChannelRead(final Object msg) { + invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg); + return this; + } +``` + + +细看OutBoundHandlerB#handlerAdded方法由两个write,一个是ctx.channel.write,另一个是ctx.write,这两个有啥区别呢?为啥输出结果是三条:ctx.channel().write -> hello world,一条hello world呢? + +启动Server启动类之后,再cmd窗口输入连接socket的命令debug之后分析得 + +``` +telnet 127.0.0.1 8888 +``` + +在客户端socket连接进Netty之后,会先注册channel并init初始化,这时会调用Server类里ServerBootstrap注入的ChannelInitilizer的initChannel方法,最终得以向ChannelPipeline里添加进OutBoundHandlerA、OutBoundHandlerB、OutBoundHandlerC,随后调用 + +```java +ch.pipeline().addLast(new xxxx) +``` +只有会触发DefaultChannelPipeline#callHandlerAdded0()方法,最终来到OutBoundHandler里的handlerAdded()方法,并向Netty的定时任务队列里添加了一个匿名内部任务,也就是: + +```java + @Override + public void handlerAdded(final ChannelHandlerContext ctx) { + ctx.executor().schedule(() -> { + ctx.channel().write("ctx.channel().write -> hello world"); + ctx.write("hello world"); + }, 3, TimeUnit.SECONDS); + } +``` + +随后完成客户端Socket的初始化工作。此时服务端的selector继续执行for死循环,执行到任务队列,此时发现任务队列中有一个定时任务需要执行,则拿出任务并执行任务,执行过程会跳转到上面的匿名内部类,并依次执行ctx.channel().write()和ctx.write()两个方法。 + +```java +ctx.channel().write() +``` +方法会从ChannelPipeline的尾部tail开始执行(上文已经总结过,outboundHandler都是从tail节点开始执行handler) ,所以字符串“ctx.channel().write -> hello world”就会按outboundHandlerC、outboundHandlerB、outboundHandlerC这个顺序开始执行,执行完head节点之后会一路往上返回到Ctx.channel().write() +方法,并最后去执行ctx.write()方法,而ctx.write()方法会从当前的handler节点开始向前执行,所以当前outboundHandlerB的前节点是outboundHandlerA,所以最终控制台打印出: +``` +OutBoundHandlerC: ctx.channel().write -> hello world +OutBoundHandlerB: ctx.channel().write -> hello world +OutBoundHandlerA: ctx.channel().write -> hello world +OutBoundHandlerA: hello world +``` + +整个过程比较复杂,也比较绕,下面用一张流程图来描述整个过程。 + +![NettyChannelPipeline流程图1](https://coderbruis.github.io/javaDocs/img/netty/source/NettyChannelPipeline流程图1.png) + + +- TODO ChannelPipeline优化?MASK +- TODO SimpleChannelInboundHandler源码分析 diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" new file mode 100644 index 0000000..986e355 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-Netty\346\234\215\345\212\241\347\253\257\345\220\257\345\212\250\345\210\206\346\236\220.md" @@ -0,0 +1,498 @@ +# Netty 服务端启动分析 + +在Java中,网络通信是通过Socket来进行的,那么在Netty中,服务端要用到的Socket是在哪里进行初始化的?并且在哪里进行accept接受客户端连接的? Netty里的Channel是啥,有啥作用呢?带着这三个问题,进入本文的Netty服务端启动分析。 + +本文分析将分为五大步: + +1. Netty中的Channel; +2. 创建服务端Channel; +3. 初始化服务端Channel; +4. 注册selector; +5. 端口绑定; + +## 1. Netty中的Channel + +在Netty中的Channel是用来定义对网络IO进行读/写的相关接口,与NIO中的Channel接口类似。Channel的功能主要有网络IO的读写、客户端发起的连接、主动关闭连接、关闭链路、获取通信双方的网络地址等。Channel接口下有一个重要的抽象类————AbstractChannel,一些公共的基础方法都在这个抽象类中实现,但对于一些特定的功能则需要不同的实现类去实现,这样最大限度地实现了功能和接口的重用。 + +在AbstractChannel中的网络IO模型和协议种类比较多,除了TCP协议,Netty还支持了HTTP2协议,如:AbstractHttp2StreamChannel。 + +Netty对于不同的网络模型以及IO模型,在AbstractChannel的基础上又抽象出了一层,如:AbstractNioChannel、AbstractEpollChannel、AbstractHttp2StreamChannel。 + +## 2. 创建服务端Channel + +创建服务端Channel又可以分为四步,如下: + +1. ServerBootstrap#bind() 用户代码入口; +2. initAndRegister() 初始化并注册; +3. newChannel() 创建服务端channel; + + +首先看下下图简易版的Netty服务端启动代码。 + +```java +public final class Server { + + public static void main(String[] args) throws Exception { + EventLoopGroup bossGroup = new NioEventLoopGroup(1); + EventLoopGroup workerGroup = new NioEventLoopGroup(); + + try { + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childOption(ChannelOption.TCP_NODELAY, true) + .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") + .handler(new ServerHandler()) + .childHandler(new ChannelInitializer() { + @Override + public void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new AuthHandler()); + //.. + + } + }); + + ChannelFuture f = b.bind(8888).sync(); + + f.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } +} +``` + +服务端构建好ServerBootstrap之后,通过bind()方法进行绑定。进入ServerBootstrap的父类AbstractBootstrap后,线程经过调用栈的调用后来到AbstractBootstrap#doBind()方法,首先就是初始化并注册Channel。 + +AbstractBootstrap#doBind() +```java + private ChannelFuture doBind(final SocketAddress localAddress) { + // 注册channel + final ChannelFuture regFuture = initAndRegister(); + final Channel channel = regFuture.channel(); + if (regFuture.cause() != null) { + return regFuture; + } + + // regFuture如果完成了,则isDone为true,否则给regFuture添加一个监听器,当完成的时候再进行doBind0的操作 + if (regFuture.isDone()) { + // 此时我们已经知道NioServerSocketChannel已经完成了注册 + ChannelPromise promise = channel.newPromise(); + doBind0(regFuture, channel, localAddress, promise); + return promise; + } else { + // Registration future is almost always fulfilled already, but just in case it's not. + final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); + + // 给regFuture添加一个监听器,当注册chanel完成的时候,会回调进来 + regFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + Throwable cause = future.cause(); + if (cause != null) { + // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an + // IllegalStateException once we try to access the EventLoop of the Channel. + promise.setFailure(cause); + } else { + // Registration was successful, so set the correct executor to use. + // See https://github.com/netty/netty/issues/2586 + promise.registered(); + + doBind0(regFuture, channel, localAddress, promise); + } + } + }); + return promise; + } + } + + final ChannelFuture initAndRegister() { + Channel channel = null; + try { + // 拿到ReflectiveChannelFactory,然后通过其newChannel生成一个服务端Channel,底层就是通过反射newInstance()获取实例 + // 这里自然是NioServerSocketChannel实例对象 + channel = channelFactory.newChannel(); + // 初始化channel + init(channel); + } catch (Throwable t) { + if (channel != null) { + // channel can be null if newChannel crashed (eg SocketException("too many open files")) + channel.unsafe().closeForcibly(); + // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor + return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t); + } + // as the Channel is not registered yet we need to force the usage of the GlobalEventExecutor + return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t); + } + + /** + * config() -> ServerBootstrapConfig + * group() -> NioEventLoopGroup,返回的是MultithreadEventLoopGroup + * register() -> 就是通过chooser选取到NioEventLoop对象 + */ + ChannelFuture regFuture = config().group().register(channel); + if (regFuture.cause() != null) { + if (channel.isRegistered()) { + channel.close(); + } else { + channel.unsafe().closeForcibly(); + } + } + return regFuture; + } +``` + +在initAndRegister处channelFactory是ReflectiveChannelFactory,具体赋值处是在ServerBootstrap#channel()方法中定义的,并且传入的channel是:NioServerSocketChannel。 + +ReflectiveChannelFactory#newChannel +```java + @Override + public T newChannel() { + try { + return constructor.newInstance(); + } catch (Throwable t) { + throw new ChannelException("Unable to create Channel from class " + constructor.getDeclaringClass(), t); + } + } +``` + +查看到ReflectiveChannelFactory#newChannel()方法,T的类型是NioServerSocketChannel,所以实际就是调用的NioServerSocketChannel#newInstance()方法反射构建一个channel对象。 + + +那么,我们看下NioServerSocketChannel底层是如何获取通过反射创建服务端Channel的呢? + + +以下部分源码均在NioServerSocketChannel类中 + +```java + public NioServerSocketChannel() { + this(newSocket(DEFAULT_SELECTOR_PROVIDER)); + } +``` + +而newSocket()方法是一个静态方法 + +```java + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + // 通过SelectorProvider来获取一个ServerSocketChannel,SelectorProvider是通过SPI来获取的 + // 此处返回一个ServerSocketChannelImpl对象 + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException( + "Failed to open a server socket.", e); + } + } +``` + +```java + public NioServerSocketChannel(ServerSocketChannel channel) { + // 调用抽象父类AbstractNioChannel构造方法,注意此处服务端Channel注册的是OP_ACCEPT事件 + super(null, channel, SelectionKey.OP_ACCEPT); + // TCP参数配置类 + config = new NioServerSocketChannelConfig(this, javaChannel().socket()); + } +``` + +AbstractNioChannel类 +```java + protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { + // 调用父类AbstractChannel的构造方法 + super(parent); + this.ch = ch; + this.readInterestOp = readInterestOp; + try { + // 关闭blocking,关闭阻塞模式:比较重要 + ch.configureBlocking(false); + } catch (IOException e) { + try { + ch.close(); + } catch (IOException e2) { + if (logger.isWarnEnabled()) { + logger.warn( + "Failed to close a partially initialized socket.", e2); + } + } + + throw new ChannelException("Failed to enter non-blocking mode.", e); + } + } +``` + +AbstractChannel类 +```java + protected AbstractChannel(Channel parent) { + this.parent = parent; + // 每个channel的唯一标识 + id = newId(); + // 底层io操作工具类 + unsafe = newUnsafe(); + // channel里的逻辑链pipeline(非常重要) + pipeline = newChannelPipeline(); + } +``` + +通过源码阅读,可以总结出Netty服务端创建Channel的三件重要事情: + +1. 通过反射来创建JDK底层的channel; +2. 设置Channel为非阻塞模式ch.configureBlocking(false); +3. 创建一个pipeline对象; + + +## 3. 初始化服务端Channel + +初始化服务端Channel可以分为如下的几步: + +1. set ChannelOptions,ChannelAttrs 设置options和attrs +2. set Child Options,ChildAttrs,为服务端创建的子链接创建options和attrs +3. config handler,配置服务端pipeline +4. add ServerBootstrapAcceptor,添加连接器 + + +ServerBoostrap端初始化过程 +```java + void init(Channel channel) throws Exception { + // 获取用户配置的options + final Map, Object> options = options0(); + synchronized (options) { + channel.config().setOptions(options); + } + + // 配置attrs + final Map, Object> attrs = attrs0(); + synchronized (attrs) { + for (Entry, Object> e: attrs.entrySet()) { + @SuppressWarnings("unchecked") + AttributeKey key = (AttributeKey) e.getKey(); + channel.attr(key).set(e.getValue()); + } + } + + ChannelPipeline p = channel.pipeline(); + + final EventLoopGroup currentChildGroup = childGroup; + final ChannelHandler currentChildHandler = childHandler; + final Entry, Object>[] currentChildOptions; + final Entry, Object>[] currentChildAttrs; + synchronized (childOptions) { + currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size())); + } + synchronized (childAttrs) { + currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size())); + } + + p.addLast(new ChannelInitializer() { + @Override + public void initChannel(Channel ch) throws Exception { + final ChannelPipeline pipeline = ch.pipeline(); + ChannelHandler handler = config.handler(); + if (handler != null) { + pipeline.addLast(handler); + } + + // 添加ServerBootstrapAccetor + ch.eventLoop().execute(new Runnable() { + @Override + public void run() { + pipeline.addLast(new ServerBootstrapAcceptor( + currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)); + } + }); + } + }); + } +``` + +## 4. 将Channel注册到selector + +整个注册selector过程可以分为以下几步: + +1. AbstractChannel$AbstractUnsafe#register(channel) +2. AbstractUnsafe#register0() +3. AbstractUnsafe#doRegister() + +AbstractChannel +```java + @Override + public final void register(EventLoop eventLoop, final ChannelPromise promise) { + if (eventLoop == null) { + throw new NullPointerException("eventLoop"); + } + if (isRegistered()) { + promise.setFailure(new IllegalStateException("registered to an event loop already")); + return; + } + if (!isCompatible(eventLoop)) { + promise.setFailure( + new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName())); + return; + } + + // 设置AbstractChannel的eventLoop + AbstractChannel.this.eventLoop = eventLoop; + + if (eventLoop.inEventLoop()) { + register0(promise); + } else { + try { + eventLoop.execute(new Runnable() { + @Override + public void run() { + register0(promise); + } + }); + } catch (Throwable t) { + logger.warn( + "Force-closing a channel whose registration task was not accepted by an event loop: {}", + AbstractChannel.this, t); + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } + } +``` + +AbstractChannel.AbstractUnsafe#register0() +```java + private void register0(ChannelPromise promise) { + try { + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + boolean firstRegistration = neverRegistered; + doRegister(); + neverRegistered = false; + registered = true; + + // 调用handlerAdd事件回调 + pipeline.invokeHandlerAddedIfNeeded(); + + safeSetSuccess(promise); + // 调用register事件回调 + pipeline.fireChannelRegistered(); + + if (isActive()) { + if (firstRegistration) { + pipeline.fireChannelActive(); + } else if (config().isAutoRead()) { + beginRead(); + } + } + } catch (Throwable t) { + closeForcibly(); + closeFuture.setClosed(); + safeSetFailure(promise, t); + } + } +``` + + +AbstractNioChannel.java +```java + @Override + protected void doRegister() throws Exception { + boolean selected = false; + // 这里是个小技巧,for(;;)比while(true)效率要高很多 + for (;;) { + try { + // 将通道channel注册到selector上 + selectionKey = javaChannel().register(eventLoop().selector, 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + eventLoop().selectNow(); + selected = true; + } else { + throw e; + } + } + } + } +``` + +就这样,NioServerSocketChannel就以Accept事件注册到了Selector上了。 + +这里需要注意一点,javaChannel()返回的是AbstractSelectableChannel,调用其register方法用于在给定的selector上注册这个通道channel,并返回一个选这件selectionKey。传入的操作位为0表示对任何事件都不感兴趣,仅仅是完成注册操作。 + +## 5. 端口绑定 + +端口绑定流程如下: + +1. AbstractBootstrap#bind() +2. AbstractBootstrap#dobind() +3. AbstractChannel#bind() +4. NioServerSocketChannel#doBind() + + +AbstractChannel.AbstractUnsafe#bind() +```java + @Override + public final void bind(final SocketAddress localAddress, final ChannelPromise promise) { + assertEventLoop(); + + if (!promise.setUncancellable() || !ensureOpen(promise)) { + return; + } + + // See: https://github.com/netty/netty/issues/576 + if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) && + localAddress instanceof InetSocketAddress && + !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() && + !PlatformDependent.isWindows() && !PlatformDependent.isRoot()) { + // Warn a user about the fact that a non-root user can't receive a + // broadcast packet on *nix if the socket is bound on non-wildcard address. + logger.warn( + "A non-root user can't receive a broadcast packet if the socket " + + "is not bound to a wildcard address; binding to a non-wildcard " + + "address (" + localAddress + ") anyway as requested."); + } + + // 是否active + boolean wasActive = isActive(); + try { + // 调用jdk底层代码进行绑定 + doBind(localAddress); + } catch (Throwable t) { + safeSetFailure(promise, t); + closeIfClosed(); + return; + } + + if (!wasActive && isActive()) { + invokeLater(new Runnable() { + @Override + public void run() { + pipeline.fireChannelActive(); + } + }); + } + safeSetSuccess(promise); + } +``` + +NioServerSocketChannel.java +```java + @Override + protected void doBind(SocketAddress localAddress) throws Exception { + // 判断jdk版本 + if (PlatformDependent.javaVersion() >= 7) { + javaChannel().bind(localAddress, config.getBacklog()); + } else { + javaChannel().socket().bind(localAddress, config.getBacklog()); + } + } +``` + + +## 总结 + +Netty服务端核心启动流程主要是为了创建NioServerSocketChannel,然后将其注册在Selector上,总结下核心步骤如下: + +- NioServerSocket#newSocket() 获取服务端channel +- ServerBootstrap#init() 对服务端channel进行初始化 +- AbstractChannel.AbstractUnsafe#register() 将服务端Channel注册到Selector上 +- AbstractChannel.AbstractUnsafe#doBind() 注册端口号 + + diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioEventLoop\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioEventLoop\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 0000000..2155406 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioEventLoop\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,607 @@ +## 1. 概述 + +在对NioEventLoop原理进行分析之前,先提出三个常见问题: + +- 默认情况下,Netty服务端起多少个线程?合适启动的线程? +- Netty是如何解决jdk的空轮训bug的? +- Netty是如何保证异步串行无锁化的? + +带着这三个问题,进入下面的NioEventLoop原理分析 + + +## 2. NioEventLoop是什么 + +这里可以先简单的将NioEventLoop理解为一个线程,但是NioEventLoop是被封装过的“线程”,这里的不同之处可以埋一个坑进行深入分析。 + +而NioEventLoop在Netty中起到什么作用呢?读完下文的你应该就会有答案了。 + +## 3. NioEventLoop的创建 + +首先,抛出一个结论,NioEventLoop是在NioEventLoopGroup中创建出来的,具体方法逻辑如下: + +NioEventLoopGroup.java + +```java + @Override + protected EventLoop newChild(Executor executor, Object... args) throws Exception { + return new NioEventLoop(this, executor, (SelectorProvider) args[0], + ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); + } +``` + +可以看出,在NioEventLoopGroup中的newChild方法入参里,传入了一个Executor对象,这是一个线程池对象,除此之外,还有可变参数args,带入到NioEventLoop的构造 +方法里,分别强转为了SelectorProvider、SelectStrategyFactory和RejectedExecutionHandler这三个对象。读到这里可能很多人都会懵,newChild是从哪里调进来的? + +为了故事的顺利发展,让我们的目光来到NioEventLoopGroup,查看下其Diagrams图。 + +![netty02png](https://coderbruis.github.io/javaDocs/img/netty/source/netty02_02.png) + +可以看到NioEventLoopGroup的顶级父接口是Executor,直白点理解NioEventLoopGroup就是一个线程池,NioEventLoop就是其创建出来的一个线程!下面看到NioEventLoopGroup的构造方法 + +NioEventLoopGroup.java +```Java + public NioEventLoopGroup() { + this(0); + } + + public NioEventLoopGroup(int nThreads) { + this(nThreads, (Executor) null); + } + + public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory) { + this(nThreads, threadFactory, SelectorProvider.provider()); + } + + public NioEventLoopGroup(int nThreads, Executor executor) { + this(nThreads, executor, SelectorProvider.provider()); + } + + public NioEventLoopGroup( + int nThreads, ThreadFactory threadFactory, final SelectorProvider selectorProvider) { + this(nThreads, threadFactory, selectorProvider, DefaultSelectStrategyFactory.INSTANCE); + } + + public NioEventLoopGroup(int nThreads, ThreadFactory threadFactory, + final SelectorProvider selectorProvider, final SelectStrategyFactory selectStrategyFactory) { + super(nThreads, threadFactory, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()); + } + + public NioEventLoopGroup( + int nThreads, Executor executor, final SelectorProvider selectorProvider) { + this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE); + } + + public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider, + final SelectStrategyFactory selectStrategyFactory) { + super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject()); + } + + public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, + final SelectorProvider selectorProvider, + final SelectStrategyFactory selectStrategyFactory) { + super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, + RejectedExecutionHandlers.reject()); + } + + public NioEventLoopGroup(int nThreads, Executor executor, EventExecutorChooserFactory chooserFactory, + final SelectorProvider selectorProvider, + final SelectStrategyFactory selectStrategyFactory, + final RejectedExecutionHandler rejectedExecutionHandler) { + super(nThreads, executor, chooserFactory, selectorProvider, selectStrategyFactory, rejectedExecutionHandler); + } +``` + +这么多的构造方法,每个方法传入的入参都不一样,可以看到默认构造方法调用的是 +```java + public NioEventLoopGroup() { + this(0); + } +``` +一路跟到父类MultithreadEventLoopGroup,可以看到如下关键代码: + +MultithreadEventLoopGroup +```java + private static final int DEFAULT_EVENT_LOOP_THREADS; + static { + DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt( + "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2)); + + if (logger.isDebugEnabled()) { + logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS); + } + } + + protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) { + super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args); + } +``` + +可以得出一个结论就是:当NioEventLoopGroup默认黄金的线程数是 2 * CPU 个。 + +NioEventLoop的创建核心逻辑在MultithreadEventExecutorGroup构造方法中,可以看到如下逻辑,分析内容待定... + +MultithreadEventExecutorGroup.java +```java + protected MultithreadEventExecutorGroup(int nThreads, Executor executor, + EventExecutorChooserFactory chooserFactory, Object... args) { + if (nThreads <= 0) { + throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads)); + } + + if (executor == null) { + executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()); + } + + children = new EventExecutor[nThreads]; + + for (int i = 0; i < nThreads; i ++) { + boolean success = false; + try { + children[i] = newChild(executor, args); + success = true; + } catch (Exception e) { + // TODO: Think about if this is a good exception type + throw new IllegalStateException("failed to create a child event loop", e); + } finally { + if (!success) { + for (int j = 0; j < i; j ++) { + children[j].shutdownGracefully(); + } + + for (int j = 0; j < i; j ++) { + EventExecutor e = children[j]; + try { + while (!e.isTerminated()) { + e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); + } + } catch (InterruptedException interrupted) { + // Let the caller handle the interruption. + Thread.currentThread().interrupt(); + break; + } + } + } + } + } + + chooser = chooserFactory.newChooser(children); + + final FutureListener terminationListener = new FutureListener() { + @Override + public void operationComplete(Future future) throws Exception { + if (terminatedChildren.incrementAndGet() == children.length) { + terminationFuture.setSuccess(null); + } + } + }; + + for (EventExecutor e: children) { + e.terminationFuture().addListener(terminationListener); + } + + Set childrenSet = new LinkedHashSet(children.length); + Collections.addAll(childrenSet, children); + readonlyChildren = Collections.unmodifiableSet(childrenSet); + } +``` + +![netty02_02png](https://coderbruis.github.io/javaDocs/img/netty/source/netty02_02.png) + +NioEventLoopGroup#newChild() +```java + @Override + protected EventLoop newChild(Executor executor, Object... args) throws Exception { + return new NioEventLoop(this, executor, (SelectorProvider) args[0], + ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]); + } +``` + +下面是NioEventLoop类的构造方法 +```java + NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider, + SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) { + super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler); + if (selectorProvider == null) { + throw new NullPointerException("selectorProvider"); + } + if (strategy == null) { + throw new NullPointerException("selectStrategy"); + } + provider = selectorProvider; + // 此处可以看出,每个NioEventLoop都会和一个Selector绑定对应 + selector = openSelector(); + selectStrategy = strategy; + } +``` + +每个NioEventLoop都会和一个Selector进行绑定对应。 + + + +SingleThreadEventLoop.java构造方法中 +```java + protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor, + boolean addTaskWakesUp, int maxPendingTasks, + RejectedExecutionHandler rejectedExecutionHandler) { + super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler); + tailTasks = newTaskQueue(maxPendingTasks); + } +``` + +NioEventLoop.java +```java + @Override + protected Queue newTaskQueue(int maxPendingTasks) { + // This event loop never calls takeTask() + return PlatformDependent.newMpscQueue(maxPendingTasks); + } +``` + +这里的任务队列有啥用? + +我们都知道,在Netty中是多线程的,即多个NioEventLoop,任务队列的作用就是当其他NioEventLoop拿到CPU的执行权时,却得到了其他线程的IO请求,此时NioEventLoop就把当前这个未处理完的请求 +以任务的形式提交到对应NioEventLoop的队列中进行串行执行,能够保证线程安全。 + + + + + + + +EventExecutorChooser是什么?有什么作用? + +在MultithreadEventExecutorGroup.java构造方法中,对EventExecutorChooser进行了赋值 + +```java + chooser = chooserFactory.newChooser(children); +``` + +DefaultEventExecutorChooserFactory#newChooser +```java + public EventExecutorChooser newChooser(EventExecutor[] executors) { + // 判断executors的数量是否是2次幂,2、4、8、16 + if (isPowerOfTwo(executors.length)) { + // 调用优化过的EventExecutorChooser + return new PowerOfTowEventExecutorChooser(executors); + } else { + return new GenericEventExecutorChooser(executors); + } + } +``` + +先看下未优化过的ExecutorChooser,原理就是对executors.length 进行取模,这样就可以对Executors的索引位进行循环使用。 +```java + private static final class GenericEventExecutorChooser implements EventExecutorChooser { + private final AtomicInteger idx = new AtomicInteger(); + private final EventExecutor[] executors; + + GenericEventExecutorChooser(EventExecutor[] executors) { + this.executors = executors; + } + + @Override + public EventExecutor next() { + return executors[Math.abs(idx.getAndIncrement() % executors.length)]; + } + } +``` + +优化过的ExecutorChooser,原理是通过&对executors进行取模操作。 +```java + private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser { + private final AtomicInteger idx = new AtomicInteger(); + private final EventExecutor[] executors; + + PowerOfTowEventExecutorChooser(EventExecutor[] executors) { + this.executors = executors; + } + + @Override + public EventExecutor next() { + return executors[idx.getAndIncrement() & executors.length - 1]; + } + } +``` + +&取模原理如下图: +![netty02_03png](https://coderbruis.github.io/javaDocs/img/netty/source/netty02_03.png) + +idx是当前索引位置,而加入executors数量为16,则16-1=15,二进制为 +``` +1111 +``` +idx二进制如下: +``` +111010 +``` + +由于是&操作,则相当于只取后四位,则idx & (Executors.length - 1) = 1010 + +如果此时idx为:1111,而此时进行&操作过后,结果就是:1111, + +则idx索引位再+1,则结果就是0000,这样就达到了循环使用索引位的效果。 + + +## 4. NioEventLoop的启动 + +先把关键步骤码出来,后面再过来分析。 + +AbstractBootstrap#doBind() +```java + private ChannelFuture doBind(final SocketAddress localAddress) { + // 初始化并注册通道Channel + final ChannelFuture regFuture = initAndRegister(); + final Channel channel = regFuture.channel(); + if (regFuture.cause() != null) { + return regFuture; + } + + // channelFuture完成了 + if (regFuture.isDone()) { + ChannelPromise promise = channel.newPromise(); + // 进入NioEventLoop的初始化 + doBind0(regFuture, channel, localAddress, promise); + return promise; + } else { + // Registration future is almost always fulfilled already, but just in case it's not. + final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel); + regFuture.addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + Throwable cause = future.cause(); + if (cause != null) { + // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an + // IllegalStateException once we try to access the EventLoop of the Channel. + promise.setFailure(cause); + } else { + // Registration was successful, so set the correct executor to use. + // See https://github.com/netty/netty/issues/2586 + promise.registered(); + + doBind0(regFuture, channel, localAddress, promise); + } + } + }); + return promise; + } + } +``` + +AbstractBootstrap.java +```java + private static void doBind0( + final ChannelFuture regFuture, final Channel channel, + final SocketAddress localAddress, final ChannelPromise promise) { + + channel.eventLoop().execute(new Runnable() { + @Override + public void run() { + if (regFuture.isSuccess()) { + channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + } else { + promise.setFailure(regFuture.cause()); + } + } + }); + } +``` + +SingleThreadEventExecutor#execute +```java + @Override + public void execute(Runnable task) { + if (task == null) { + throw new NullPointerException("task"); + } + + boolean inEventLoop = inEventLoop(); + addTask(task); + if (!inEventLoop) { + startThread(); + if (isShutdown()) { + boolean reject = false; + try { + if (removeTask(task)) { + reject = true; + } + } catch (UnsupportedOperationException e) { + // The task queue does not support removal so the best thing we can do is to just move on and + // hope we will be able to pick-up the task before its completely terminated. + // In worst case we will log on termination. + } + if (reject) { + reject(); + } + } + } + + if (!addTaskWakesUp && wakesUpForTask(task)) { + wakeup(inEventLoop); + } + } + + private void startThread() { + if (state == ST_NOT_STARTED) { + if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) { + boolean success = false; + try { + doStartThread(); + success = true; + } finally { + if (!success) { + STATE_UPDATER.compareAndSet(this, ST_STARTED, ST_NOT_STARTED); + } + } + } + } + } + + private void doStartThread() { + assert thread == null; + executor.execute(new Runnable() { + @Override + public void run() { + thread = Thread.currentThread(); + if (interrupted) { + thread.interrupt(); + } + + boolean success = false; + updateLastExecutionTime(); + try { + SingleThreadEventExecutor.this.run(); + success = true; + } catch (Throwable t) { + logger.warn("Unexpected exception from an event executor: ", t); + } finally { + for (;;) { + int oldState = state; + if (oldState >= ST_SHUTTING_DOWN || STATE_UPDATER.compareAndSet( + SingleThreadEventExecutor.this, oldState, ST_SHUTTING_DOWN)) { + break; + } + } + + // Check if confirmShutdown() was called at the end of the loop. + if (success && gracefulShutdownStartTime == 0) { + if (logger.isErrorEnabled()) { + logger.error("Buggy " + EventExecutor.class.getSimpleName() + " implementation; " + + SingleThreadEventExecutor.class.getSimpleName() + ".confirmShutdown() must " + + "be called before run() implementation terminates."); + } + } + + try { + // Run all remaining tasks and shutdown hooks. + for (;;) { + if (confirmShutdown()) { + break; + } + } + } finally { + try { + cleanup(); + } finally { + // Lets remove all FastThreadLocals for the Thread as we are about to terminate and notify + // the future. The user may block on the future and once it unblocks the JVM may terminate + // and start unloading classes. + // See https://github.com/netty/netty/issues/6596. + FastThreadLocal.removeAll(); + + STATE_UPDATER.set(SingleThreadEventExecutor.this, ST_TERMINATED); + threadLock.countDown(); + if (logger.isWarnEnabled() && !taskQueue.isEmpty()) { + logger.warn("An event executor terminated with " + + "non-empty task queue (" + taskQueue.size() + ')'); + } + terminationFuture.setSuccess(null); + } + } + } + } + }); + } +``` + +就这样,调用了NioEventLoop的run方法,进行了NioEventLoop启动 +NioEventLoop#run +```java + @Override + protected void run() { + for (;;) { + try { + try { + switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { + case SelectStrategy.CONTINUE: + continue; + + case SelectStrategy.BUSY_WAIT: + // fall-through to SELECT since the busy-wait is not supported with NIO + + case SelectStrategy.SELECT: + select(wakenUp.getAndSet(false)); + + // 'wakenUp.compareAndSet(false, true)' is always evaluated + // before calling 'selector.wakeup()' to reduce the wake-up + // overhead. (Selector.wakeup() is an expensive operation.) + // + // However, there is a race condition in this approach. + // The race condition is triggered when 'wakenUp' is set to + // true too early. + // + // 'wakenUp' is set to true too early if: + // 1) Selector is waken up between 'wakenUp.set(false)' and + // 'selector.select(...)'. (BAD) + // 2) Selector is waken up between 'selector.select(...)' and + // 'if (wakenUp.get()) { ... }'. (OK) + // + // In the first case, 'wakenUp' is set to true and the + // following 'selector.select(...)' will wake up immediately. + // Until 'wakenUp' is set to false again in the next round, + // 'wakenUp.compareAndSet(false, true)' will fail, and therefore + // any attempt to wake up the Selector will fail, too, causing + // the following 'selector.select(...)' call to block + // unnecessarily. + // + // To fix this problem, we wake up the selector again if wakenUp + // is true immediately after selector.select(...). + // It is inefficient in that it wakes up the selector for both + // the first case (BAD - wake-up required) and the second case + // (OK - no wake-up required). + + if (wakenUp.get()) { + selector.wakeup(); + } + // fall through + default: + } + } catch (IOException e) { + // If we receive an IOException here its because the Selector is messed up. Let's rebuild + // the selector and retry. https://github.com/netty/netty/issues/8566 + rebuildSelector0(); + handleLoopException(e); + continue; + } + + cancelledKeys = 0; + needsToSelectAgain = false; + final int ioRatio = this.ioRatio; + if (ioRatio == 100) { + try { + processSelectedKeys(); + } finally { + // Ensure we always run tasks. + runAllTasks(); + } + } else { + final long ioStartTime = System.nanoTime(); + try { + processSelectedKeys(); + } finally { + // Ensure we always run tasks. + final long ioTime = System.nanoTime() - ioStartTime; + runAllTasks(ioTime * (100 - ioRatio) / ioRatio); + } + } + } catch (Throwable t) { + handleLoopException(t); + } + // Always handle shutdown even if the loop processing threw an exception. + try { + if (isShuttingDown()) { + closeAll(); + if (confirmShutdown()) { + return; + } + } + } catch (Throwable t) { + handleLoopException(t); + } + } + } +``` + + +## 5. NioEventLoop执行逻辑 + +未完待续... \ No newline at end of file diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" new file mode 100644 index 0000000..05499f2 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-NioServerSocketChannel\346\216\245\345\217\227\346\225\260\346\215\256\345\216\237\347\220\206\345\210\206\346\236\220.md" @@ -0,0 +1,264 @@ +## NioServerSocketChannel读取数据原理分析 + +NioServerSocketChannel是AbstractNioMessageChannel的子类,而NioSocketChannel是AbstractNioByteChannel的子类,并且他们都有两个公共的父类:AbstractNioChannel、AbstractChannel。 + +在Netty中Channel是用来定义对网络IO的读写操作的相关接口,与NIO的Channel接口类似。Channel的功能主要有网络IO的读写、客户端发起的连接、主动关闭连接、关闭链路、获取通信双方的网络地址等。 +一些公共的基础方法都在这个AbstractChannel抽象类中实现,几个核心的方法如:channel的注册,channel撤销注册,网络IO的读、写。但对于一些特定的功能则需要不同的实现类去实现,这样最大限度地实现了功能和接口的重用, +就如AbstractNioChannel中主要定义了doRegister()、doConnect()、newDirectBuffer()方法。 + +## 1. NioServerSocketChannel源码分析 + +NioServerSocketChannel是AbstractNioMessageChannel的子类,由于它由服务端使用,并且只负责监听Socket的接入,不关心IO的读写,所以与NioSocketChannel相比要简单得多。 + +NioServerSocketChannel封装了NIO中的ServerSocketChannel,并通过newSocket()方法打开了ServerSocketChannel + +NioServerSocketChannel.class + +```java + private static ServerSocketChannel newSocket(SelectorProvider provider) { + try { + return provider.openServerSocketChannel(); + } catch (IOException e) { + throw new ChannelException( + "Failed to open a server socket.", e); + } + } +``` + +对于NioServerSocketChannel注册至selector上的操作,是在AbstractNioChannel中实现的,源码如下: + +```java + @Override + protected void doRegister() throws Exception { + boolean selected = false; + for (;;) { + try { + selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this); + return; + } catch (CancelledKeyException e) { + if (!selected) { + eventLoop().selectNow(); + selected = true; + } else { + throw e; + } + } + } + } +``` + +在ServerSocketChannel的开启,selector上的注册等前期工作完成后,NioServerSocketChannel的开始监听新连接的加入,源码如下: + +```java + @Override + protected int doReadMessages(List buf) throws Exception { + // 拿到jdk底层channel + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + if (ch != null) { + // new出一个NioSocketChannel,将jdk SocketChannel封装成NioSocketChannel,并且这里给NioSocketChannel注册了一个SelectionKey.OP_READ事件 + buf.add(new NioSocketChannel(this, ch)); // 往buf里写入NioSocketChannel + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +``` + +上面的源码展示了Netty最终拿到新连接请求后,将jdk底层的SocketChannel封装NioSocketChannel的过程,那么selector是如何获取到accept事件后,调用到这个doReadMessages方法的呢? + +为了分析原理的延续,故事还要回到bossGroup的NioEventLoop里,当bossGroup启动,NioServerSocketChannel实例新建并注册到selector之后,Netty的bossGroup就会运行一个NioEventLoop,它的核心工作就是作为一个selector一直去监听客户端发出的accept、connect、read、write等事件。具体逻辑查看NioEventLoop#run()方法,详细的原理请回看之前的NioEventLoop的原理分析,此处只分析NioEventLoop#run()获取到链接事件到调用NioServerSocketChannel#doReadMessages()的链路。 + +1. NioEventLoop#run()一直轮训,监听这客户端发出的事件,在轮训过程中如果有任务产生,则会优先执行这些任务,调用非阻塞的selectNow(),否则调用select(deadlineNanos)阻塞指定时间去监听客户端事件。 +2. 调用NioEventLoop#processSelectedKeys(),Netty默认用的是优化过后的selectedKey,所以调用的是NioEventLoop#processSelectedKeysOptimized()方法。 +3. 在processSelectedKeysOptimized方法里会遍历selectedKeys,去拿selectedKeys中的SelectionKey,这个key就是从网络中获取到的感兴趣事件。 +4. 先通过SelectionKey获取attachment,及对应的事件channel。由于这里是获取的是accept事件,所以SelectionKey#attachment()获取到的是NioServerSocketChannel对象。 +5. 在NioEventLoop#processSelectedKey()方法中,首先拿到NioServerSocketChannel父类AbstractNioMessageChannel中的NioMessageUnsafe对象,接着根据readyOps进行判断,这里当然就是SelectionKey.OP_ACCEPT事件。 +6. 调用NioMessageUnsafe#read()方法,最终该方法调用了NioServerSocketChannel#doReadMessages(),完了之后会新建一个对SelectionKey.OP_READ事件感兴趣的NioSocketChannel对象,并存放在readBuf的一个集合中。 +7. 接着调用ChannelPipeline#fireChannelRead()方法,目的在于最终调用ServerBootstrapAcceptor#channelRead()方法,调用childGroup#register(child),把新建的NioSocketChannel对象注册到selector上。 + +这样,NioServerSocketChannel监听accept事件,接收到客户端连接后,封装客户端的“连接”到NioSocketChannel对象,并注册到selector上,后面的网络IO的读写操作都由这个NioSocketChannel对象来负责处理。 + +上述核心的6步源码如下: + +NioEventLoop.class +```java + @Override + protected void run() { + for (;;) { + try { + try { + switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks())) { + // ... 省略 + case SelectStrategy.SELECT: + select(wakenUp.getAndSet(false)); + // ... 省略 + if (wakenUp.get()) { + selector.wakeup(); + } + // fall through + default: + } + } catch (IOException e) { + rebuildSelector0(); + handleLoopException(e); + continue; + } + // ... 省略 + + // 步骤1 + processSelectedKeys(); + runAllTasks(); + + // ... 省略 + } catch (Throwable t) { + handleLoopException(t); + // ... 省略 + } + } + } +``` + +NioEventLoop.class +```java + // 步骤2 + private void processSelectedKeysOptimized() { + for (int i = 0; i < selectedKeys.size; ++i) { + // 步骤3 + final SelectionKey k = selectedKeys.keys[i]; + selectedKeys.keys[i] = null; + + // 步骤4 + final Object a = k.attachment(); + + if (a instanceof AbstractNioChannel) { + // 步骤5 + processSelectedKey(k, (AbstractNioChannel) a); + } else { + @SuppressWarnings("unchecked") + NioTask task = (NioTask) a; + processSelectedKey(k, task); + } + + if (needsToSelectAgain) { + selectedKeys.reset(i + 1); + + selectAgain(); + i = -1; + } + } + } +``` + +NioEventLoop.class +```java + private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) { + final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe(); + if (!k.isValid()) { + final EventLoop eventLoop; + try { + eventLoop = ch.eventLoop(); + } catch (Throwable ignored) { + return; + } + if (eventLoop != this || eventLoop == null) { + return; + } + unsafe.close(unsafe.voidPromise()); + return; + } + + try { + int readyOps = k.readyOps(); + if ((readyOps & SelectionKey.OP_CONNECT) != 0) { + int ops = k.interestOps(); + ops &= ~SelectionKey.OP_CONNECT; + k.interestOps(ops); + + unsafe.finishConnect(); + } + + if ((readyOps & SelectionKey.OP_WRITE) != 0) { + ch.unsafe().forceFlush(); + } + + // 步骤5 + if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) { + unsafe.read(); + } + } catch (CancelledKeyException ignored) { + unsafe.close(unsafe.voidPromise()); + } + } +``` + +NioServerSocketChannel.class + +```java + @Override + protected int doReadMessages(List buf) throws Exception { + // 拿到jdk 的SocketChannel,代表着和客户端的一个连接socket + SocketChannel ch = SocketUtils.accept(javaChannel()); + + try { + if (ch != null) { + // 步骤6 + // 封装一个NioSocketChannel对象,并且设置感兴趣事件为:SelectionKey.OP_READ + buf.add(new NioSocketChannel(this, ch)); + return 1; + } + } catch (Throwable t) { + logger.warn("Failed to create a new channel from an accepted socket.", t); + + try { + ch.close(); + } catch (Throwable t2) { + logger.warn("Failed to close a socket.", t2); + } + } + + return 0; + } +``` + +ServerBootstrapAcceptor.class + +```java + public void channelRead(ChannelHandlerContext ctx, Object msg) { + final Channel child = (Channel) msg; + + child.pipeline().addLast(childHandler); + + setChannelOptions(child, childOptions, logger); + setAttributes(child, childAttrs); + + try { + // 步骤7 + // 在workerGroup的NioEventLoop上的selector注册了NioSocketChannel + childGroup.register(child).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + forceClose(child, future.cause()); + } + } + }); + } catch (Throwable t) { + forceClose(child, t); + } + } +``` + +以上就是Netty中有关NioServerSocketChannel读取数据的底层原理分析。 + +下一篇分析NioSocketChannel的发送、读取数据底层原理。 \ No newline at end of file diff --git "a/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-\345\210\235\345\247\213Netty\345\217\212\345\205\266\346\236\266\346\236\204.md" "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-\345\210\235\345\247\213Netty\345\217\212\345\205\266\346\236\266\346\236\204.md" new file mode 100644 index 0000000..d721716 --- /dev/null +++ "b/note/Netty/Netty\345\272\225\345\261\202\346\272\220\347\240\201\350\247\243\346\236\220-\345\210\235\345\247\213Netty\345\217\212\345\205\266\346\236\266\346\236\204.md" @@ -0,0 +1,220 @@ +## 1. 回顾BIO和NIO + +```java +public class BIO { + public static void main(String[] args) throws Exception { + ServerSocket serverSocket = new ServerSocket(666); + System.out.println("Server started..."); + while (true) { + System.out.println("socket accepting..."); + Socket socket = serverSocket.accept(); + new Thread(new Runnable() { + @Override + public void run() { + try { + byte[] bytes = new byte[1024]; + InputStream inputStream = socket.getInputStream(); + while (true) { + System.out.println("reading..."); + int read = inputStream.read(bytes); + if (read != -1) { + System.out.println(new String(bytes, 0, read)); + } else { + break; + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + try { + socket.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + }).start(); + } + } +} +``` + +BIO流程图如下: + +![first-netty01](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-01.png) + +BIO缺陷: + +1. BIO中,作为服务端开发,使用ServerSocket 绑定端口号之后会监听该端口,等待accept事件,accept是会阻塞当前线程; +2. 当我们收到accept事件的时候,程序就会拿到客户端与当前服务端连接的Socket,针对这个socket我们可以进行读写,但是呢,这个socket读写都是会阻塞当前线程的; +3. 一般我们会有使用多线程方式进行c/s交互,但是这样很难做到C10K(比如说:1W个客户端就需要和服务端用1W个线程支持,这样的话CPU肯定就爆炸了,同时线程上下文切换也会把机器负载给拉飞) + +NIO流程图如下: + +![first-netty02](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-02.png) + +针对BIO的以上不足,NIO提供了解决方案,即NIO提供的selector。在NIO中,提供一个selector用于监听客户端socket的连接事件,当有socket连接进来之后,就需要把检查的客户端socket注册到这个selector中,对于客户端socket来说,其线程就阻塞在了selector的select方法中,此时客户端程序该干啥干啥,不需要像BIO一样维护一个长连接去等待事件。 + +只有当客户端的selector发现socket就绪了有事件了,才会唤醒线程去处理就绪状态的socket。 + + +当然,NIO也有许多的不足,归纳为以下几点: +1. NIO 的类库和 API 繁杂,使用麻烦:需要熟练掌握 Selector、ServerSocketChannel、SocketChannel、ByteBuffer等; +2. 需要具备其他的额外技能:要熟悉 Java 多线程编程,因为 NIO 编程涉及到 Reactor 模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的 NIO 程序; +3. 开发工作量和难度都非常大:例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等等; +4. JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU100%。直到 JDK1.7 版本该问题仍旧存在,没有被根本解决; + +## 2. Why Netty?What is Netty? + +为什么出现Netty? +Netty 对 JDK 自带的 NIO 的 API 进行了封装,解决了上述问题。 + +1. 设计优雅:适用于各种传输类型的统一 API 阻塞和非阻塞 Socket;基于灵活且可扩展的事件模型,可以清晰地分离关注点;高度可定制的线程模型-单线程,一个或多个线程池; +2. 使用方便:详细记录的 Javadoc,用户指南和示例; +3. 高性能、吞吐量更高:延迟更低;减少资源消耗;最小化不必要的内存复制; +4. 安全:完整的 SSL/TLS 和 StartTLS 支持; +5. 社区活跃、不断更新:社区活跃,版本迭代周期短,发现的 Bug 可以被及时修复,同时,更多的新功能会被加入; + +Netty是什么? + +1. Netty是一个异步的、基于事件驱动的网络应用框架,可以用于快速开发高性能、高可靠的网络IO程序; +2. Netty 主要针对在 TCP 协议下,面向 Client 端的高并发应用,或者 Peer-to-Peer 场景下的大量数据持续传输的应用; +3. Netty 本质是一个 NIO 框架,Netty在NIO基础上进行了二次封装,所以学习Netty之前得了解NIO基础知识; +4. Netty是作为许多开源框架的底层,例如:Dubbo、RocketMQ、ElasticSearch等 + + + +## 3. Netty为什么性能这么高呢? + +小伙伴肯定会非常好奇,Netty为什么能作为这么多优秀框架的底层实现?Netty为什么这么高性能? + +Netty的高性能主要可以总结为如下几点: + +1. Netty作为异步事件驱动的网络,高性能之处主要来自于其I/O模型和线程处理模型,不同于传统BIO,客户端的连接以及事件处理都阻塞在同一个线程里,Netty则将客户端的线程和处理客户端的线程分离开来;(高效的Reactor线程模型) +2. Netty的IO线程NioEventLoop由于聚合了多路复用器Selector,可以同时并发处理成百上千个客户端连接;(IO多路复用模型) +3. Netty底层还实现了零拷贝,避免了IO过程中数据在操作系统底层来回”无效的“拷贝和系统态切换;(零拷贝) +4. 无锁串行化设计,串行设计:消息的处理尽可能在一个线程内完成,期间不进行线程切换,避免了多线程竞争和同步锁的使用;(单线程) +5. Netty 默认提供了对Google Protobuf 的支持,通过扩展Netty 的编解码接口,可以实现其它的高性能序列化框架(高性能的序列化框架) +6. Netty中大量使用了volatile,读写锁,CAS和原子类;(高效并发编程) +7. Netty的内存分配管理实现非常高效,Netty内存管理分为了池化(Pooled)和非池化(UnPooled),heap(堆内内存)和direct(堆外内存),对于Netty默认使用的是池化内存管理,其内部维护了一个内存池可以循环的创建ByteBuf(Netty底层实现的一个Buffer),提升了内存的使用效率,降低由于高负载导致的频繁GC。同时Netty底层实现了jemalloc算法(jemalloc3实现的满二叉树,读内存进行一个分隔、jemalloc4则优化了jemalloc3的算法,实现了将内存切割成了一个二维数组维护的一个数据结构,提升了内存的使用率)(Netty内存管理非常高效) + +基于以上的这么多的优点,是非常推荐阅读Netty底层源码。 + +TODO + +## 4. 线程模型基本介绍 + +1. 不同的线程模式,对程序的性能有很大影响,为了搞清 Netty 线程模式,我们来系统的讲解下各个线程模式,最后看看 Netty 线程模型有什么优越性; +2. 目前存在的线程模型有:传统阻塞 I/O 服务模型 和Reactor 模式; +3. 根据 Reactor 的数量和处理资源池线程的数量不同,有 3 种典型的实现: + - 单Reactor单线程; + - 单Reactor多线程; + - 主从Reactor多线程; +4. Netty 线程模式(Netty 主要基于主从 Reactor 多线程模型做了一定的改进,其中主从 Reactor 多线程模型有多个 Reactor) + +### 4.1 传统BIO线程模型 + +![first-netty03](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-03.png) + +模型特点 +1. 采用阻塞 IO 模式获取输入的数据; +2. 每个连接都需要独立的线程完成数据的输入,业务处理,数据返回; + +问题分析 +1. 当并发数很大,就会创建大量的线程,占用很大系统资源; +2. 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在 Handler对象中的read 操作,导致上面的处理线程资源浪费; + +### 4.2 Reactor 模式(单Reactor单线程) + +I/O 复用结合线程池,就是 Reactor 模式基本设计思想,如图: +![first-netty04](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-04.png) + +针对传统阻塞 I/O 服务模型的 2 个缺点,解决方案: + +基于 I/O 多路复用模型:多个连接共用一个阻塞对象ServiceHandler,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。 + +1. 基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。(解决了当并发数很大时,会创建大量线程,占用很大系统资源) +2. 基于 I/O 复用模型:多个客户端进行连接,先把连接请求给ServiceHandler。多个连接共用一个阻塞对象ServiceHandler。假设,当C1连接没有数据要处理时,C1客户端只需要阻塞于ServiceHandler,C1之前的处理线程便可以处理其他有数据的连接,不会造成线程资源的浪费。当C1连接再次有数据时,ServiceHandler根据线程池的空闲状态,将请求分发给空闲的线程来处理C1连接的任务。(解决了线程资源浪费的那个问题) + +由上引出单Reactor单线程模型图: +![first-netty05](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-05.png) + +方案说明 +1. select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个select方法来监听多路连接请求 +2. Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发 +3. 如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理 +4. 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应 +5. Handler 会完成 Read → 业务处理 → Send 的完整业务流程 + +优缺点分析 +1. 优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成 +2. 缺点:性能问题,只有一个线程,无法完全发挥多核 CPU 的性能。Handler在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈 +3. 缺点:可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障 +4. 使用场景:客户端的数量有限,业务处理非常快速,比如 Redis 在业务处理的时间复杂度 O(1) 的情况 + +### 4.3 单Reactor多线程 + +![first-netty06](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-06.png) + +方案说明 +1. Reactor 对象通过 Select 监控客户端请求事件,收到事件后,通过 Dispatch 进行分发 +2. 如果是建立连接请求,则由 Acceptor 通过 accept 处理连接请求,然后创建一个 Handler 对象处理完成连接后的各种事件 +3. 如果不是连接请求,则由 Reactor 分发调用连接对应的 handler 来处理(也就是说连接已经建立,后续客户端再来请求,那基本就是数据请求了,直接调用之前为这个连接创建好的handler来处理) +4. handler 只负责响应事件,不做具体的业务处理(这样不会使handler阻塞太久),通过 read 读取数据后,会分发给后面的 worker 线程池的某个线程处理业务。【业务处理是最费时的,所以将业务处理交给线程池去执行】 +5. worker 线程池会分配独立线程完成真正的业务,并将结果返回给 handler +6. handler 收到响应后,通过 send 将结果返回给 client + +优缺点分析 +1. 优点:可以充分的利用多核 cpu 的处理能力 +2. 缺点:多线程数据共享和访问比较复杂。Reactor 承担所有的事件的监听和响应,它是单线程运行,在高并发场景容易出现性能瓶颈。也就是说Reactor主线程承担了过多的事 + +### 4.4 主从Reactor多线程 + +针对单 Reactor 多线程模型中,Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 Reactor 在多线程中运行 +![first-netty07](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-07.png) + +SubReactor是可以有多个的,如果只有一个SubReactor的话那和单 Reactor 多线程就没什么区别了。 + +方案分析 +1. Reactor 主线程 MainReactor 对象通过 select 监听连接事件,收到事件后,通过 Acceptor 处理连接事件 +2. 当 Acceptor 处理连接事件后,MainReactor 将连接分配给 SubReactor +3. subreactor 将连接加入到连接队列进行监听,并创建 handler 进行各种事件处理 +4. 当有新事件发生时,subreactor 就会调用对应的 handler 处理 +5. handler 通过 read 读取数据,分发给后面的 worker 线程处理 +6. worker 线程池分配独立的 worker 线程进行业务处理,并返回结果 +7. handler 收到响应的结果后,再通过 send 将结果返回给 client +8. Reactor 主线程可以对应多个 Reactor 子线程,即 MainRecator 可以关联多个 SubReactor + +方案优缺点分析 +1. 优点:父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。 +2. 优点:父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线程,子线程无需返回数据。 +3. 缺点:编程复杂度较高 + + +这种主从多线程模型在许多优秀的框架中都使用到了,包括Nginx主从Reactor多线程模型,Netty主从多线程模型等。 + +对于Reactor模式,小结一下: +1. 响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的(比如你第一个SubReactor阻塞了,我可以调下一个 SubReactor为客户端服务) +2. 可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销 +3. 扩展性好,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源 +4. 复用性好,Reactor 模型本身与具体事件处理逻辑无关,具有很高的复用性 + +### 4.5 Netty中的线程模型 + +![first-netty08](https://coderbruis.github.io/javaDocs/img/netty/source/first-netty-08.png) + +Netty线程模型流程分析 +1. Netty 抽象出两组线程池 ,BossGroup 专门负责接收客户端的连接,WorkerGroup 专门负责网络的读写 +2. BossGroup 和 WorkerGroup 类型都是 NioEventLoopGroup +3. NioEventLoopGroup 相当于一个事件循环组,这个组中含有多个事件循环,每一个事件循环是 NioEventLoop +4. NioEventLoop 表示一个不断循环的执行处理任务的线程,每个 NioEventLoop 都有一个 Selector,用于监听绑定在其上的 socket 的网络通讯 +5. NioEventLoopGroup 可以有多个线程,即可以含有多个 NioEventLoop +6. 每个 BossGroup下面的NioEventLoop 循环执行的步骤有 3 步 + - 轮询 accept 事件 + - 处理 accept 事件,与 client 建立连接,生成 NioScocketChannel,并将其注册到某个 workerGroup NIOEventLoop 上的 Selector + - 继续处理任务队列的任务,即 runAllTasks +7. 每个 WorkerGroup NIOEventLoop 循环执行的步骤 + - 轮询 read,write 事件 + - 处理 I/O 事件,即 read,write 事件,在对应 NioScocketChannel 处理 + - 处理任务队列的任务,即 runAllTasks +8. 每个 Worker NIOEventLoop 处理业务时,会使用 pipeline(管道),pipeline 中包含了 channel(通道),即通过 pipeline 可以获取到对应通道,管道中维护了很多的处理器 \ No newline at end of file diff --git "a/note/Netty/\344\272\214\350\277\233\345\210\266.md" "b/note/Netty/\344\272\214\350\277\233\345\210\266.md" index 69bfb3b..97f7667 100644 --- "a/note/Netty/\344\272\214\350\277\233\345\210\266.md" +++ "b/note/Netty/\344\272\214\350\277\233\345\210\266.md" @@ -48,7 +48,7 @@ 对于二进制运算,记住一个口诀: 1. 与(&)运算 - + 运算规则: ``` 0&0=0, 0&1=0, 1&0=0, 1&1=1 ``` @@ -143,7 +143,7 @@ a = 10 **在开源框架底层中算法会用到大量的二进制运算,** 例如:在最近学习的Netty底层源码中,DefaultEventExecutorChooserFactory的底层源码有一个方法, 就是通过 a & (-a)来运算的。 -``` +```Java @Override public EventExecutorChooser newChooser(EventExecutor[] executors) { if (isPowerOfTwo(executors.length)) { diff --git "a/note/Netty/\351\233\266\346\213\267\350\264\235.md" "b/note/Netty/\351\233\266\346\213\267\350\264\235.md" index 6d41d8a..151082b 100644 --- "a/note/Netty/\351\233\266\346\213\267\350\264\235.md" +++ "b/note/Netty/\351\233\266\346\213\267\350\264\235.md" @@ -4,7 +4,20 @@ ## 正文 -### 1. mmap和sendFile零拷贝 +### 1. 再看IO + +在深入零拷贝机制之前,先来了解下传统BIO通信底层发生了什么,为什么会这么“消耗资源”。Linux服务器是现在绝大多数系统的首选,它的优点就不再赘述,下面的分析都基于Linux环境来进行。作为一台服务器,最常见的功能就是 +获取客户端发送过来的请求,然后再去查数据库DB获取到想要的数据,再将数据以一定的业务逻辑处理后传回给客户端,这一过程主要会调用Linux内核的以下两个函数: + +``` +read(file, tmp_buf, len); + +write(socket, tmp_buf, len); +``` + + + +### 2. mmap和sendFile零拷贝 在Java程序中,常用的零拷贝有mmap(内存映射)和sendFile。让我们回到IO底层,在Java中,一次IO操作在操作系统底层都做了哪些工作。 diff --git "a/note/Spring/\344\273\216Spring\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\342\200\224\342\200\224\347\255\226\347\225\245\346\250\241\345\274\217.md" "b/note/Spring/\344\273\216Spring\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\342\200\224\342\200\224\347\255\226\347\225\245\346\250\241\345\274\217.md" index a35adcc..d26db14 100644 --- "a/note/Spring/\344\273\216Spring\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\342\200\224\342\200\224\347\255\226\347\225\245\346\250\241\345\274\217.md" +++ "b/note/Spring/\344\273\216Spring\346\272\220\347\240\201\344\270\255\345\255\246\344\271\240\342\200\224\342\200\224\347\255\226\347\225\245\346\250\241\345\274\217.md" @@ -24,7 +24,7 @@ ### 一、先看看初学者都会的多重if-else判断 -``` +```Java public int count(int num1, int num2, String operation) { if (operation.equals("+")) { return num1 + num2; @@ -45,7 +45,7 @@ public int count(int num1, int num2, String operation) { ### 二、策略模式实现 #### 2.1 定义一个策略接口: Strategy.class -``` +```Java public interface Strategy { public int doOperation(int num1, int num2); } @@ -54,7 +54,7 @@ public interface Strategy { #### 2.2 创建接口的实现类 Add.java -``` +```Java public class Add implements Strategy{ @Override public int doOperation(int num1, int num2) { @@ -64,7 +64,7 @@ public class Add implements Strategy{ ``` Substract.java -``` +```Java public class Substract implements Strategy{ @Override public int doOperation(int num1, int num2) { @@ -74,7 +74,7 @@ public class Substract implements Strategy{ ``` Multiply.java -``` +```Java public class Multiply implements Strategy{ @Override public int doOperation(int num1, int num2) { @@ -84,7 +84,7 @@ public class Multiply implements Strategy{ ``` Divide.java -``` +```Java public class Divide implements Strategy{ @Override public int doOperation(int num1, int num2) { @@ -95,7 +95,7 @@ public class Divide implements Strategy{ #### 2.3 创建Context类 -``` +```Java public class Context { private Strategy strategy; @@ -110,7 +110,7 @@ public class Context { ``` #### 2.4 创建实现类 -``` +```Java public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(); @@ -158,7 +158,7 @@ public class StrategyPatternDemo { 在学习BeanDefinitionReader之前,要先了解一下什么是BeanDefinition 接口BeanDefinition.java -``` +```Java public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON; String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE; @@ -172,7 +172,7 @@ public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement { 可以看到BeanDefinition作为一个接口,主要是用于存储从XML配置文件读取Bean信息到JVM内存的一个载体,具体是存储在了BeanDefinition的实现类——RootBeanDefinition中,下面来看看RootBeanDefinition。 -``` +```Java public class RootBeanDefinition extends AbstractBeanDefinition { @Nullable private BeanDefinitionHolder decoratedDefinition; @@ -187,7 +187,7 @@ public class RootBeanDefinition extends AbstractBeanDefinition { } ``` 可以看到RootBeanDefinition不是真正存储Bean信息的载体,继续查看BeanDefinitionHolder -``` +```Java public class BeanDefinitionHolder implements BeanMetadataElement { private final BeanDefinition beanDefinition; @@ -244,13 +244,13 @@ public interface ResourceLoader { 由于```resourceLoader instanceof ResourcePatternResolver为true```,所以走如下逻辑: AbstractBeanDefinitionReader.java -``` +```Java Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); int count = loadBeanDefinitions(resources); ``` AbstractApplicationContext.java -``` +```Java @Override public Resource[] getResources(String locationPattern) throws IOException { return this.resourcePatternResolver.getResources(locationPattern); @@ -258,7 +258,7 @@ AbstractApplicationContext.java ``` PathMatchingResourcePatternResolver.java -``` +```Java @Override public Resource[] getResources(String locationPattern) throws IOException { diff --git "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" index dc0a23f..248f495 100644 --- "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" +++ "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\212\357\274\211.md" @@ -17,10 +17,10 @@ Spring容器就相当于一个大的水桶,里面装着很多水——bean对 ## 进入正题 在Spring容器的设计中,有两个主要的容器系列,一个是实现BeanFactory接口的简单容器系列,这个接口实现了容器最基本的功能;另一个是ApplicationContext应用上下文,作为容器的高级形态而存在,它用于扩展BeanFactory中现有的功能。ApplicationContext和BeanFactory两者都是用于加载Bean的,但是相比之下,ApplicationContext提供了更多的扩展功能,简单一点说:ApplicationContext包含BeanFactory的所有功能。绝大多数“典型”的企业应用和系统,ApplicationContext就是你需要使用的。下面展示一下分别使用BeanFactory和ApplicationContext读取xml配置文件的方式: -``` +```Java BeanFactory bf = new XmlBeanFactory(new ClassPathResource("applicationContext.xml")); ``` -``` +```Java ApplicationContext bf = new ClassPathXmlApplicationContext("applicationContext.xml"); ``` 下面先介绍Spring最核心的两个类。 @@ -28,7 +28,7 @@ ApplicationContext bf = new ClassPathXmlApplicationContext("applicationContext.x ### DefaultListableBeanFactory DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现。下面看看DefaultListableBeanFactory的层次结构图。 -![图片1](https://note.youdao.com/yws/api/personal/file/A91C9C5BB33B48A4B501435C157FFD99?method=download&shareKey=2b9a7ef7fd42d051fec83fe3f5eef7a8) +![spring-01](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-01.png) 从上往下开始介绍各个类以及接口的作用: - AliasRegistry(接口):alias指的是bean的别名,而aliasRegistry定义了对alias的增删改查等操作。 - SimpleAliasRegistry(类):主要使用map作为alias的缓存,并对接口AliasRegistry进行实现。 @@ -48,7 +48,7 @@ DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册 ### XmlBeanDefinitionReader XML配置文件的读取是Spring中最重要的功能,因为Spring的大部分功能都是以配置作为切入点的,XmlBeanDefinitionReader实现了对资源文件的读取、解析以及注册。先看一下XmlBeanDefinitionReader的层次结构图。 -![图片2](https://note.youdao.com/yws/api/personal/file/477FF4A409A94CBB8CF9A05A16D8F7D4?method=download&shareKey=de7d24b623d4c5bb7e65bb440438e271) +![spring-02](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-02.png) - EnvironmentCapable(接口):定义获取Environment方法,Environment代表了配置文件。 - BeanDefinitionReader(接口):主要定义资源文件读取并转换为BeanDefinition的各个功能。 @@ -63,7 +63,7 @@ XML配置文件的读取是Spring中最重要的功能,因为Spring的大部 下面演示一个使用ApplicationContext接口获取xml配置,从而实现一个helloword级别的spring程序: applicationContext.xml -``` +```Java ``` 测试类 -``` +```Java public class SpringMain { public static void main(String[] args) { //使用spring容器 @@ -96,16 +96,16 @@ Person{name='Bruis', age=23} ** 前方高能 ** -![图片3](https://note.youdao.com/yws/api/personal/file/219238FD61C146C99E137E303D52EA66?method=download&shareKey=d5e5aaa1e9fa782eeb056b89119c3565) +![spring-03](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-03.jpg) 通过在断点debug,跟踪程序运行。 1. SpringMain.class -``` +```Java ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); ``` 2. ClassPathXmlApplicationContext.class -``` +```Java public ClassPathXmlApplicationContext(String configLocation) throws BeansException { this(new String[]{configLocation}, true, (ApplicationContext)null); } @@ -123,7 +123,7 @@ public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, } ``` 3. AbstractRefreshableConfigApplicationContext.class -``` +```Java //给configLocations字符串数组设置值,支持多个配置文件已数组方式同时传入。 public void setConfigLocations(String... locations) { if (locations != null) { @@ -146,10 +146,10 @@ public void setConfigLocations(String... locations) { 下面我们来重点看看refresh()过程。 -![Image](https://note.youdao.com/yws/api/personal/file/76AE8FEDAFF54B6881C336B056AC5B0A?method=download&shareKey=430f5263180efd8467df6e6434456f3d) +![spring-04](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-04.jpg) 1. AbstractApplicationContext.class -``` +```Java /* 简单来说,Spring容器的初始化时右refresh()方法来启动的,这个方法标志着IOC容器的正式启动。具体来说,这里的启动包括了BeanDefinition和Resource的定位、载入和注册三个基本过程。 */ @@ -198,7 +198,7 @@ public void refresh() throws BeansException, IllegalStateException { } ``` 2. AbstractRefreshableApplicationContext.class -``` +```Java /* 通知子类刷新内部bean工厂,初始化BeanFactory并进行XML文件的解析、读取。obtain就是指获得的含义,这个方法obtaiinFreshBeanFactory正是实现BeanFactory的地方,也就是经过这个方法,ApplicationContext就已经拥有了BeanFactory的全部功能(也就是BeanFactory包含在了Spring容器里了)。 */ @@ -239,14 +239,14 @@ protected final void refreshBeanFactory() throws BeansException { } ``` 这里先看看上面代码的loadBeanDefinitions()方法运行完后的结果 -![图片](https://note.youdao.com/yws/api/personal/file/59FBCD3CC1B54136A05309EA6B88FEB3?method=download&shareKey=80bdcfcbde0362b73eb633390c5b1042) -![图片](https://note.youdao.com/yws/api/personal/file/E258907852284A6F93A2C305319EBB64?method=download&shareKey=7e1dba96d3b53ca9b6af017552f8fd31) +![spring-05](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-05.png) +![spring-06](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-06.png) 从图中可以知道,loadBeanDefinitions()方法运行完后,在beanFactory变量里面存放着一个ConcurrentHashMap变量,用于存放着person这个KV键值对,Key为person,Value为一个ArrayList的变量,里面存放着person的两个属性:age、name。 那么,person的属性是怎么被封装到beanFactory里面的呢?请看下面的源码解析。 3. AbstractXmlApplicationContext.class -``` +```Java protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException { //为给定的BeanFactory创建一个新的XmlBeanDefinitionReader XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory); @@ -272,7 +272,7 @@ protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansE 首先在refreshBeanFactory()方法中已经初始化了DefaultListableBeanFactory,对于读取XML配置文件,还需要使用XmlBeanDefinitionReader。所以在上述loadBeanDefinitions()中就需要初始化XmlBeanDefinitionReader。在DefaultListableBeanFactory和XmlBeanDefinitionReader后就可以进行配置文件的读取了。要注意的地方时,在XmlBeanDefinitionReader初始化时就已经把DefaultListableBeanFactory给注册进去了,所以在XmlBeanDefinitionReader读取的BeanDefinition都会注册到DefaultListableBeanFactory中,也就是经过上述的loadingBeanDefinitions(),类型DefaultListableBeanFactory的变量beanFactory就已经包含了所有**解析好的配置**了。 4. AbstractBeanDefinitionReader.class -``` +```Java @Override public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException { Assert.notNull(locations, "Location array must not be null"); @@ -321,7 +321,7 @@ public int loadBeanDefinitions(String location, @Nullable Set actualRe } ``` 5. PathMatchingResourcePatternResolver.class -``` +```Java @Override public Resource[] getResources(String locationPattern) throws IOException { Assert.notNull(locationPattern, "Location pattern must not be null"); @@ -352,7 +352,7 @@ public Resource[] getResources(String locationPattern) throws IOException { } ``` 6. XmlBeanDefinitionReader.class -``` +```Java /* 从XML配置文件中获取bean定义信息 */ @@ -409,10 +409,10 @@ protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource) 下面,继续深入registerBeanDefinitions方法。 -![图片](https://note.youdao.com/yws/api/personal/file/861658D89B0D4B48A7ED56B554CF3028?method=download&shareKey=c3bc974e751495bac74d9ac9ec56cb75) +![spring-07](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-07.jpg) 1. XmlBeanDefinitionReader.class -``` +```Java /* 注册给定DOM文档中包含的bean定义 */ @@ -424,7 +424,7 @@ public int registerBeanDefinitions(Document doc, Resource resource) throws BeanD } ``` 2. DefaultBeanDefinitionDocumentReader.class -``` +```Java /* 此实现根据“spring-beans”XSD解析bean定义 */ @@ -507,7 +507,7 @@ protected void processBeanDefinition(Element ele, BeanDefinitionParserDelegate d ``` 2. BeanDefinitionParserDelegate.class -``` +```Java /* 解析bean定义本身,而不考虑名称或别名,如果解析期间出错则返回null。 */ @@ -636,11 +636,11 @@ public void parsePropertyElement(Element ele, BeanDefinition bd) { } ``` -![Images](https://note.youdao.com/yws/api/personal/file/75CAC9D21AD64BAB89B0D25C8BBE7598?method=download&shareKey=89e73cf46fe18b1b85aecf8d58006f8e) -![Images](https://note.youdao.com/yws/api/personal/file/CF65BB80EB934EBEBA49466CFAB261A0?method=download&shareKey=8b9f0078cf5a3171dfd69d00d9ba55f6) +![spring-08](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-08.png) +![spring-09](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-09.png) 然后,就会一路返回到refresh()方法里的加载bean定义信息的方法——loadBeanDefinitions(),此时beanFactory里面就会存在一个带有KV对的ConcurrentHashMap,而这个beanFactory会存放在Spring容器里面。 -``` +```Java DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); customizeBeanFactory(beanFactory); @@ -648,8 +648,8 @@ customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); ``` 再看看DefaultListableBeanFactory里面的内容 -![Images](https://note.youdao.com/yws/api/personal/file/59FBCD3CC1B54136A05309EA6B88FEB3?method=download&shareKey=80bdcfcbde0362b73eb633390c5b1042) -![Images](https://note.youdao.com/yws/api/personal/file/E258907852284A6F93A2C305319EBB64?method=download&shareKey=7e1dba96d3b53ca9b6af017552f8fd31) +![spring-10](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-10.png) +![spring-11](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-11.png) 上面的过程,就已经完成了Spring容器的初始化过程,相信读者也已经对Spring容器的初始化有了一个大致的了解。下面总结一下Spring容器的初始化: - 第一个过程是Resource定位过程。这个Resource定位过程指的是BeanDefinition的资源定位,它由ResourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用都提供了统一接口。这个定位过程类似于容器寻找数据的过程,就像使用水桶装水先要把水找到一样。 @@ -670,10 +670,10 @@ bean的创建和初始化过程是在refresh方法里的invokeBeanFactoryPostPro - 当容器关闭时,调用Bean的销毁方法 下面先看看创建bean和初始化bean的时序图。 -![Images](https://note.youdao.com/yws/api/personal/file/8B415614A97D45B481925159264C344F?method=download&shareKey=1083828cfcea581b0aa5cae56e3f3090) +![spring-12](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-12.jpg) 1. AbstractApplicationContext.class -``` +```Java public void refresh() throws BeansException, IllegalStateException { ... // 实例剩余的(非懒加载)的单例 @@ -719,7 +719,7 @@ protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory b 这里的懒加载的意思,指的是bean单例不是在Spring容器初始化的时候就创建的,而是在要使用该bean的时候,才会创建该bean。 2. DefaultListableBeanFactory.class -``` +```Java // 实例剩余的(非懒加载)的单例 @Override public void preInstantiateSingletons() throws BeansException { @@ -780,7 +780,7 @@ public void preInstantiateSingletons() throws BeansException { ``` 3. AbstractBeanFactory.class -``` +```Java protected T doGetBean(final String name, @Nullable final Class requiredType, @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException { // 去除name上存在的工厂bean的前缀 @@ -895,7 +895,7 @@ protected T doGetBean(final String name, @Nullable final Class requiredTy ``` 4. DefaultSingletonBeanRegistry.class -``` +```Java /* 尝试从缓存中获取单例对象,如果缓存中有该单例对象,并且该对象正在被创建,则从缓存中获取。 */ @@ -967,10 +967,10 @@ public Object getSingleton(String beanName, ObjectFactory singletonFactory) { ``` 无图无真相: -![Images](https://note.youdao.com/yws/api/personal/file/4C30C0DA143E422FBD27E50AE71AC179?method=download&shareKey=2f4dff65df0e9761ede47d26782dd977) +![spring-13](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-13.png) 5. AbstractAutowireCapableBeanFactory.class -``` +```Java /* 该类的中心方法:创建bean实例,实例化bean实例,应用bean的后置处理器 diff --git "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" index faa364d..89fdf81 100644 --- "a/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" +++ "b/note/Spring/\346\267\261\345\205\245Spring\346\272\220\347\240\201\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224\346\267\261\345\205\245Spring\345\256\271\345\231\250\357\274\214\351\200\232\350\277\207\346\272\220\347\240\201\351\230\205\350\257\273\345\222\214\346\227\266\345\272\217\345\233\276\346\235\245\345\275\273\345\272\225\345\274\204\346\207\202Spring\345\256\271\345\231\250\357\274\210\344\270\213\357\274\211.md" @@ -79,12 +79,12 @@ SpringIOC容器是如何在Web环境中被加载并起作用的?SpringIOC容 IOC容器的启动过程就是建立Spring上下文的过程,该上下文是与ServletContext相伴而生的,同时也是IOC容器在Web应用环境中的具体表现之一。由ContextLoaderListener启动的上下文为根上下文。在根上下文的基础上,还有一个与Web MVC相关的上下文应用来保存控制器(DispatcherServlet)需要的MVC对象,**作为根上下文的子上下文**,构成一个层次化的上下文体系,这个与Web MVC相关的上下文——WebApplicationContext。在Web容器中启动Spring应用程序时,首先建立根上下文,然后建立这个上下文体系,这个上下文体系的建立是由ContextLoader来完成的。简单点说,ContextLoaderListener的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。 先看看Web程序启动到SpringIOC容器创建和初始化的整个过程。 -![image](https://note.youdao.com/yws/api/personal/file/9755412D703C4DE287B26AF2396E57BD?method=download&shareKey=52783ceb34f405ad47140c22da34275e) +![spring-14](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-14.jpg) 结合着时序图,再去调试源码,思路会清晰很多。 ContextLoaderListener.class -``` +```Java public class ContextLoaderListener extends ContextLoader implements ServletContextListener { public ContextLoaderListener() { @@ -117,7 +117,7 @@ public class ContextLoaderListener extends ContextLoader implements ServletConte 这里的ContextLoaderListener是Spring的类,但实现了ServletContextListener接口。这个接口是Servlet API中定义的,提供了与Servlet生命周期结合的回调,也就是说Servlet调用contextInitialized()方法初始化容器时,会回调ContextLoaderListener中实现的contextInitialized()方法,Servlet中的contextDestroyed()方法也同理。观察源码可知,在Web容器中,建立WebApplicationContext的过程是在contextInitialized()方法中完成的。 ContextLoader.class -``` +```Java public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { ... // 判断在web容器中是否存在WebApplicationContext,因为在配置中只允许申明一次ServletContextListener,多次声明会扰乱Spring的执行逻辑。 @@ -168,7 +168,7 @@ public WebApplicationContext initWebApplicationContext(ServletContext servletCon 由ContextLoader的源码可知,SpringIOC的载入过程是在ContextLoader类的initWebApplicationContext()方法中完成的。 这里还要介绍一个重要的接口——WebApplicationContext -``` +```Java public interface WebApplicationContext extends ApplicationContext { /** @@ -184,7 +184,7 @@ public interface WebApplicationContext extends ApplicationContext { } ``` 而WebApplicationContext接口是由XMLWebApplicationContext来实现具体的功能,然后再通过ApplicationContext接口与BeanFactory接口对接,完成Spring容器的功能。然而对于具体的一些Spring容器的实现都是在AbstractRefreshableWebApplicationContext中完成的,这一点和**上篇**讲解的AbstractRefreshableConfigApplicationContext功能类似。initWebApplicationContext()方法最后返回的是一个WebApplicationContext接口,而实际返回的就是XMLWebApplicationContext实现类。XMLWebApplicationContext在基本的ApplicationContext功能的基础上,增加了对**Web环境**和XML配置定义的处理。在XMLWebApplicationContext的初始化过程中,Web容器中的IOC容器被建立起来,从而再整个Web容器中建立起Spring应用。 -``` +```Java public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationContext { /** 默认读取Spring配置文件的根路径,如果指定其他配置文件,则从这个默认的根路径读取。 */ @@ -234,7 +234,7 @@ public class XmlWebApplicationContext extends AbstractRefreshableWebApplicationC 从源码中可以看到,XMLWebApplicationContext中成员变量存放着默认的读取Spring配置文件的根目录,在生成IOC容器过程中,就会从默认路径/WEB-INF/applicationContext.xml配置文件中或者指定的配置文件路径获取,然后再通过熟悉的loadBeanDefinitions()方法来获取Bean定义信息,最终完成整个上下文的初始化过程。 ContextLoader.class -``` +```Java protected WebApplicationContext createWebApplicationContext(ServletContext sc) { // 这里判断使用什么样的类在Web容器中作为IOC容器 Class contextClass = determineContextClass(sc); @@ -272,9 +272,9 @@ protected Class determineContextClass(ServletContext servletContext) { ``` 下面看看默认的IOC容器是什么。有图有真相: -![image](https://note.youdao.com/yws/api/personal/file/AB1007BC2A7549D7898417D6231AE4E3?method=download&shareKey=e851d344aedd461f319dba3b8e2c6fe8) +![spring-15](https://github.com/coderbruis/JavaSourceCodeLearning/blob/master/note/images/spring/spring-15.jpg) -``` +```Java protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) { if (ObjectUtils.identityToString(wac).equals(wac.getId())) { String idParam = sc.getInitParameter(CONTEXT_ID_PARAM); diff --git "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224\346\263\250\345\206\214AnnotationAwareAspectJAutoProxyCreator.md" "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224\346\263\250\345\206\214AnnotationAwareAspectJAutoProxyCreator.md" index 5003158..014dd27 100644 --- "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224\346\263\250\345\206\214AnnotationAwareAspectJAutoProxyCreator.md" +++ "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224\346\263\250\345\206\214AnnotationAwareAspectJAutoProxyCreator.md" @@ -13,7 +13,7 @@ - TestMain //main测试方法入口 TestBean.java -``` +```Java public class TestBean { private String testStr = "testStr"; @@ -32,7 +32,7 @@ public class TestBean { ``` AspectJTest.java -``` +```Java @Aspect public class AspectJTest { @@ -96,7 +96,7 @@ aspectTest.xml ``` TestMain.java -``` +```Java public class TestMain { public static void main(String[] args) { ApplicationContext context = new ClassPathXmlApplicationContext("aspectTest.xml"); @@ -157,7 +157,7 @@ http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNam 无图无真相啊,原来Spring将配置文件中的xmlns配置都解析成了一个一个Java命名解析器。回到我们的关注重点——AopNamespaceHandler,查看源码: AopNamespaceHandler.class -``` +```Java public class AopNamespaceHandler extends NamespaceHandlerSupport { public AopNamespaceHandler() { } @@ -171,7 +171,7 @@ public class AopNamespaceHandler extends NamespaceHandlerSupport { } ``` 可以看到,在init()方法里,Spring对aspectj-autoproxy也就是AnnotationAwareAspectJAutoProxyCreator进行了注册。在详细了解注册原理之前,先说明下在Spring中,所有的解析器都是对BeanDefinitionParser接口的同一实现: -``` +```Java public interface BeanDefinitionParser { @Nullable BeanDefinition parse(Element var1, ParserContext var2); @@ -180,7 +180,7 @@ public interface BeanDefinitionParser { 解析入口都是从parse方法开始的。 进入AspectJAutoProxyBeanDefinitionParser类中查看parse的实现逻辑: -``` +```Java class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { ... @Nullable @@ -196,7 +196,7 @@ class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { AopNamspaceUtils -``` +```Java public abstract class AopNamespaceUtils { public static final String PROXY_TARGET_CLASS_ATTRIBUTE = "proxy-target-class"; private static final String EXPOSE_PROXY_ATTRIBUTE = "expose-proxy"; @@ -231,7 +231,7 @@ public abstract class AopNamespaceUtils { ``` -``` +```Java public abstract class AopConfigUtils { @Nullable private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, @Nullable Object source) { @@ -264,7 +264,7 @@ registerOrEscalateApcAsRequired方法的作用就是获取AnnotationAwareAspectJ 看看如果proxy-target-class和expose-proxy都为true时,代码的逻辑。 -``` +```Java public abstract class AopConfigUtils { ... /* @@ -307,7 +307,7 @@ public abstract class AopConfigUtils { 经过了useClassProxyingIfNecessary()方法的调用,ParserContext对象中存放好了注册的额外信息(proxy-target-class、expose-proxy值等),这里暂且将ParserContext称为解析上下文。由上面的源码可知,在AopNamespaceUtils类的registerAspectJAnnotationAutoProxyCreatorIfNecessary方法中,将获取的org.springframework.aop.config.internalAutoProxyCreator的BeanDefinition和解析上下文一起传入registerComponentIfNecessary方法中,进行Component组件注册。 在随后的registerComponentIfNecessary方法中,经过new BeanComponentDefinition()构造方法的调用,已经将AnnotationAwareAspectJAutoProxyCreator的BeanDefinition注册到了SpringIOC中。 -``` +```Java public abstract class AopConfigUtils { ... private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, ParserContext parserContext) { @@ -318,14 +318,14 @@ public abstract class AopConfigUtils { } } ``` -``` +```Java public class BeanComponentDefinition extends BeanDefinitionHolder implements ComponentDefinition { public BeanComponentDefinition(BeanDefinition beanDefinition, String beanName) { this(new BeanDefinitionHolder(beanDefinition, beanName)); } } ``` -``` +```Java public class BeanDefinitionHolder implements BeanMetadataElement { public BeanDefinitionHolder(BeanDefinition beanDefinition, String beanName) { this(beanDefinition, beanName, (String[])null); @@ -341,7 +341,7 @@ public class BeanDefinitionHolder implements BeanMetadataElement { } ``` 然后一路返回,将BeanDefinition存放在解析上下文(ParserContext)中,并在AspectJAutoProxyBeanDefinitionParser类的extendBeanDefinition方法中取出。 -``` +```Java class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser { private void extendBeanDefinition(Element element, ParserContext parserContext) { BeanDefinition beanDef = parserContext.getRegistry().getBeanDefinition("org.springframework.aop.config.internalAutoProxyCreator"); diff --git "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224\346\217\255\345\274\200JDK\345\212\250\346\200\201\344\273\243\347\220\206\345\222\214CGLIB\344\273\243\347\220\206\347\232\204\347\245\236\347\247\230\351\235\242\347\272\261.md" "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224\346\217\255\345\274\200JDK\345\212\250\346\200\201\344\273\243\347\220\206\345\222\214CGLIB\344\273\243\347\220\206\347\232\204\347\245\236\347\247\230\351\235\242\347\272\261.md" index a83ee68..026a120 100644 --- "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224\346\217\255\345\274\200JDK\345\212\250\346\200\201\344\273\243\347\220\206\345\222\214CGLIB\344\273\243\347\220\206\347\232\204\347\245\236\347\247\230\351\235\242\347\272\261.md" +++ "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224\346\217\255\345\274\200JDK\345\212\250\346\200\201\344\273\243\347\220\206\345\222\214CGLIB\344\273\243\347\220\206\347\232\204\347\245\236\347\247\230\351\235\242\347\272\261.md" @@ -14,7 +14,7 @@ #### 1.1 引入简单的CGLIB例子 在讲解CGLIB动态代理之前,先看一下最简单的CGLIB动态代理的例子。 -``` +```Java import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; @@ -71,7 +71,7 @@ com.bruis.learnaop.testcglibaop.EnhancerDemo$$EnhancerByCGLIB$$413eae0d@53e25b76 回到SpringAOP源码。在《深入学习SpringAOP源码(二)》中,介绍到DefaultAopProxyFactory源码部分 -``` +```Java public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) { @@ -88,7 +88,7 @@ public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { } ``` 从createAopProxy()源码中可以看到,创建SpringAOP有两种方式,一、JDK动态代理;二、CGLIB动态代理;点进ObjenesisCglibAopProxy源码,发现它继承了CglibAopFactory -``` +```Java class ObjenesisCglibAopProxy extends CglibAopProxy { protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) { // 通过增强器获取代理类的class对象 @@ -122,7 +122,7 @@ class ObjenesisCglibAopProxy extends CglibAopProxy { createProxyClassAndInstance方法和前面总结的CGLIB创建代理的步骤一样。 继续查看CglibAopProxy是如何准备Enhancer增强器以及创建拦截器链的。 -``` +```Java class CglibAopProxy implements AopProxy, Serializable { public Object getProxy(@Nullable ClassLoader classLoader) { if (logger.isTraceEnabled()) { @@ -229,7 +229,7 @@ class CglibAopProxy implements AopProxy, Serializable { #### 1.3 DynamicAdvisedInterceptor都做了些啥工作? -``` +```Java private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable { @Nullable public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { @@ -280,7 +280,7 @@ class CglibAopProxy implements AopProxy, Serializable { #### 1.4 啥是拦截器链?拦截器链从哪获取? 啥是拦截器链?从哪获取拦截器链?下面继续深入DefaultAdvisorChainFactory方法的getInterceptorsAndDynamicInterceptionAdvice()方法 -``` +```Java public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializable { public List getInterceptorsAndDynamicInterceptionAdvice(Advised config, Method method, @Nullable Class targetClass) { /* @@ -371,7 +371,7 @@ public class DefaultAdvisorChainFactory implements AdvisorChainFactory, Serializ 在这过程中,DefaultAdvisorAdapterRegistry扮演者非常关键的角色。 -``` +```Java public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { private final List adapters = new ArrayList(3); @@ -451,7 +451,7 @@ DefaultAdvisorAdapterRegistry类主要负责: #### 1.5 调用拦截器链的proceed方法 视线回到DynamicAdvisedInterceptor的intercept方法,在 -``` +```Java List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); ``` 执行完成之后,chain中存放好了拦截器链,分别是 @@ -460,7 +460,7 @@ List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(me 3. AspectJAroundAdvice 4. MethodBeforeAdviceInterceptor -``` +```Java List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); Object retVal; if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) { @@ -477,7 +477,7 @@ List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(me **后置通知实现逻辑:** -``` +```Java public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodInterceptor, AfterAdvice, Serializable { public Object invoke(MethodInvocation mi) throws Throwable { Object var2; @@ -492,7 +492,7 @@ public class AspectJAfterAdvice extends AbstractAspectJAdvice implements MethodI } ``` -``` +```Java public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Cloneable { protected final Object proxy; @@ -535,7 +535,7 @@ public class ReflectiveMethodInvocation implements ProxyMethodInvocation, Clonea **环绕通知实现逻辑:** -``` +```Java public class AspectJAroundAdvice extends AbstractAspectJAdvice implements MethodInterceptor, Serializable { public Object invoke(MethodInvocation mi) throws Throwable { if (!(mi instanceof ProxyMethodInvocation)) { @@ -553,7 +553,7 @@ public class AspectJAroundAdvice extends AbstractAspectJAdvice implements Method } } ``` -``` +```Java public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedenceInformation, Serializable { protected Object invokeAdviceMethod(JoinPoint jp, @Nullable JoinPointMatch jpMatch, @Nullable Object returnValue, @Nullable Throwable t) throws Throwable { return this.invokeAdviceMethodWithGivenArgs(this.argBinding(jp, jpMatch, returnValue, t)); @@ -582,7 +582,7 @@ public abstract class AbstractAspectJAdvice implements Advice, AspectJPrecedence 3. invokeAdviceMethodWithGivenArgs方法调用aspectJAdviceMethod.invoke方法,调用AspectJTest类中aroundTest方法 **前置通知实现逻辑:** -``` +```Java public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable { private final MethodBeforeAdvice advice; diff --git "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \346\267\261\345\205\245AnnotationAwareAspectJAutoProxyCreator.md" "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \346\267\261\345\205\245AnnotationAwareAspectJAutoProxyCreator.md" index fd88853..034a001 100644 --- "a/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \346\267\261\345\205\245AnnotationAwareAspectJAutoProxyCreator.md" +++ "b/note/SpringAOP/\346\267\261\345\205\245\345\255\246\344\271\240SpringAOP\346\272\220\347\240\201\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \346\267\261\345\205\245AnnotationAwareAspectJAutoProxyCreator.md" @@ -26,7 +26,7 @@ 将视线转移到AbstractAutowireCapableBeanFactory: -``` +```Java public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory { // 在实例化AnnotationAwareAspectJAutoProxyCreator之前进行解析 @Nullable @@ -55,7 +55,7 @@ public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFac resolveBeforeInstantiation()方法调用了AbstractAutoProxyCreator()的postProcessBeforeInstantiation()和postProcessAfterInstantiation()。 AbstractAutoProxyCreator.class -``` +```Java import ... public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { /* @@ -142,7 +142,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport imp ReflectiveAspectJAdvisorFactory的getAdvisors()中主要的工作是:迭代出@AspectJ注解修饰的类的方法,然后拿着这些方法区尝试获取Advisor,最后存在advisors集合里。 -``` +```Java public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable { //Spring将@AspectJ注解的beanName和bean工厂封装为了MetadataAwareAspectInstanceFactory public List getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) { @@ -186,7 +186,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto ``` getAdvisorMethods方法中通过反射工具来获取Advisor方法。 -``` +```Java public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFactory implements Serializable { private List getAdvisorMethods(Class aspectClass) { List methods = new ArrayList(); @@ -203,7 +203,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto ``` 视线来到ReflectiveAspectJAdvisorFactory的getAdvisor方法 -``` +```Java @Nullable public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory, int declarationOrderInAspect, String aspectName) { this.validate(aspectInstanceFactory.getAspectMetadata().getAspectClass()); @@ -243,7 +243,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto 1. 从缓存中获取通知 2. 创建代理 -``` +```Java protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; @@ -269,7 +269,7 @@ public class ReflectiveAspectJAdvisorFactory extends AbstractAspectJAdvisorFacto } ``` -``` +```Java public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator { ... @Nullable @@ -299,7 +299,7 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC } ``` -``` +```Java public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorAutoProxyCreator { protected List findCandidateAdvisors() { /* @@ -317,7 +317,7 @@ public class AnnotationAwareAspectJAutoProxyCreator extends AspectJAwareAdvisorA } ``` -``` +```Java public class BeanFactoryAspectJAdvisorsBuilder { ... public List buildAspectJAdvisors() { @@ -399,7 +399,7 @@ public class BeanFactoryAspectJAdvisorsBuilder { ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190823164940860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 回到方法 -``` +```Java protected List findEligibleAdvisors(Class beanClass, String beanName) { List candidateAdvisors = this.findCandidateAdvisors(); /* @@ -416,7 +416,7 @@ protected List findEligibleAdvisors(Class beanClass, String beanName ``` findCandidateAdvisors()完成的是通知的解析工作,但是并不是所有的通知都适用于当前bean的,还要选出适合的通知。选择逻辑在findAdvisorsTahtCanApply方法里。 -``` +```Java public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator { ... protected List findAdvisorsThatCanApply(List candidateAdvisors, Class beanClass, String beanName) { @@ -434,7 +434,7 @@ public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyC } ``` -``` +```Java public abstract class AopUtils { public static List findAdvisorsThatCanApply(List candidateAdvisors, Class clazz) { if (candidateAdvisors.isEmpty()) { @@ -541,7 +541,7 @@ public abstract class AopUtils { 回到AbstractAutoProxyCreator的wrapIfNecessary方法中。经过this.getAdvicesAndAdvisorsForBean()方法的工作,获取到了可应用的通知对象数组,接下来的工作就是要对这些通知进行代理了。 -``` +```Java public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware { ... protected Object createProxy(Class beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { @@ -614,7 +614,7 @@ public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport imp } ``` -``` +```Java public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Serializable { public Advisor wrap(Object adviceObject) throws UnknownAdviceTypeException { // 如果封装对象本身就是Advisor,则无需做任何处理 @@ -658,7 +658,7 @@ public class DefaultAdvisorAdapterRegistry implements AdvisorAdapterRegistry, Se **获取代理方式** -``` +```Java public class ProxyFactory extends ProxyCreatorSupport { ... public Object getProxy(@Nullable ClassLoader classLoader) { @@ -666,7 +666,7 @@ public class ProxyFactory extends ProxyCreatorSupport { } } ``` -``` +```Java public class ProxyCreatorSupport extends AdvisedSupport { protected final synchronized AopProxy createAopProxy() { if (!this.active) { @@ -678,7 +678,7 @@ public class ProxyCreatorSupport extends AdvisedSupport { } ``` -``` +```Java public class DefaultAopProxyFactory implements AopProxyFactory, Serializable { public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { // 如果aop配置文件没有配置属性属性,则返回JdkDynamicAopProxy的实例对象 diff --git "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224SpringFactoriesLoader.md" "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224SpringFactoriesLoader.md" index d059b10..881b18d 100644 --- "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224SpringFactoriesLoader.md" +++ "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224SpringFactoriesLoader.md" @@ -31,14 +31,14 @@ SpringFactoriesLoader通过loadFactories方法来加载并实例化来自FACTORI ### 2. 上源码  首先,可以看到SpringFactoriesLoader是final类,final修饰的类是不可以被继承,类中的方法都是不可以被覆盖的,且默认都是final修饰的方法,可以猜想到SpringFactoriesLoader类在被设计之初,是不想开发者继承该类并对该类进行扩展。所以,如果在开发中不想让别人对你的类继承或者扩展,那就用final来修饰吧~~ -``` +```Java public final class SpringFactoriesLoader { } ``` 下面看下SpringFactoriesLoader类有哪些成员变量? -``` +```Java /** * 寻找工厂的位置 * 工厂可以存放在多个jar文件中 @@ -53,7 +53,7 @@ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factor 在spring.factories文件中,有非常多的工厂类,包括了属性源加载器、错误报告器、容器初始化器、容器监听器等,这些工厂类在SpringBoot中都有非常重要的作用,具体的读者可以自行前往查看。 -``` +```Java // 自定义的用于存储工厂的缓存 private static final Map> cache = new ConcurrentReferenceHashMap<>(); @@ -89,7 +89,7 @@ private static final Map> cache = new  loadFactories方法通过类加载器来加载并且实例化FACTORIES_RESOURCE_LOCATION路径文件中定义的工厂实现。在返回工厂之前,都会通过AnnotationAwareOrderComparator这个类来进行排序。如果需要自定义实例化策略,请使用loadFactoryNames去获取所有注册的工厂名称。  loadFactories方法中,入参factoryType表示工厂类的接口或者抽象类;入参classLoader表示加载工厂的类加载器,如果为空则会使用默认的类加载器。 -``` +```Java public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader) { Assert.notNull(factoryType, "'factoryType' must not be null"); // 类加载器 @@ -114,7 +114,7 @@ public static List loadFactories(Class factoryType, @Nullable ClassLoa } ``` -``` +```Java private static T instantiateFactory(String factoryImplementationName, Class factoryType, ClassLoader classLoader) { try { // 通过classUtils工具类获取工厂实现类的Class对象 @@ -147,7 +147,7 @@ public static List loadFactories(Class factoryType, @Nullable ClassLoa #### 2.2 loadFactoryNames方法  由于loadFactoryNames方法的注释和loadFactories内容一样,所以这里就不写出来了。 -``` +```Java public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) { // 获取到factoryType工厂类型 String factoryTypeName = factoryType.getName(); @@ -156,7 +156,7 @@ public static List loadFactories(Class factoryType, @Nullable ClassLoa } ``` -``` +```Java private static Map> loadSpringFactories(@Nullable ClassLoader classLoader) { // 从缓存中获取已经加载过的SpringFactories MultiValueMap result = cache.get(classLoader); @@ -216,4 +216,4 @@ public static List loadFactories(Class factoryType, @Nullable ClassLoa > 觉得作者写的不错的点个赞,关注作者。 > 本文 Github https://github.com/coderbruis/JavaSourceLearning 已收录,更多源码文章以及源码在github中可以学习。 - + diff --git "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\345\220\257\345\212\250\345\212\240\350\275\275\345\231\250.md" "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\345\220\257\345\212\250\345\212\240\350\275\275\345\231\250.md" index d533a52..247898b 100644 --- "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\345\220\257\345\212\250\345\212\240\350\275\275\345\231\250.md" +++ "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\345\220\257\345\212\250\345\212\240\350\275\275\345\231\250.md" @@ -8,7 +8,7 @@ SpringBoot的CommandLineRunner是一个启动加载器的核心,CommandLinerRunner是一个接口,该接口定义如下: -``` +```Java @FunctionalInterface public interface CommandLineRunner { @@ -22,7 +22,7 @@ public interface CommandLineRunner { 实例代码: -``` +```Java @Component @Order(1) public class FirstCommandLineRunner implements CommandLineRunner { @@ -47,7 +47,7 @@ public class FirstCommandLineRunner implements CommandLineRunner { ### 2. ApplicationRunner 实现SpringBoot启动加载器的第二种方式,就是通过实现ApplicationRunner,先看下ApplicationRunner的源码: -``` +```Java @FunctionalInterface public interface ApplicationRunner { void run(ApplicationArguments args) throws Exception; @@ -57,7 +57,7 @@ public interface ApplicationRunner { 先看下ApplicationArguments这个接口定义: -``` +```Java public interface ApplicationArguments { // 获取源参数,即SpringBootApplication#run方法中传入的args; 这里的source就是SpringBoot对象 String[] getSourceArgs(); @@ -76,7 +76,7 @@ public interface ApplicationArguments { 实例代码: -``` +```Java @Order(1) @Component public class FirstApplicationRunner implements ApplicationRunner { @@ -98,7 +98,7 @@ public class FirstApplicationRunner implements ApplicationRunner { ### 3. SpringBoot启动加载器原理 -其实SpringBoot启动加载器原理比较简单,在底层源码调用逻辑比较清除。 +其实SpringBoot启动加载器原理比较简单,在底层源码调用逻辑比较清楚。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200820100314418.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center) 在DefaultApplicationArguments里,有一个不可忽略的类:Source diff --git "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\233\221\345\220\254\345\231\250\344\270\216\344\272\213\344\273\266\346\234\272\345\210\266.md" "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\233\221\345\220\254\345\231\250\344\270\216\344\272\213\344\273\266\346\234\272\345\210\266.md" index 1552d4c..dedae1a 100644 --- "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\233\221\345\220\254\345\231\250\344\270\216\344\272\213\344\273\266\346\234\272\345\210\266.md" +++ "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\233\221\345\220\254\345\231\250\344\270\216\344\272\213\344\273\266\346\234\272\345\210\266.md" @@ -33,7 +33,7 @@ 下面看看ApplicationListener接口定义信息 -``` +```Java /** * Interface to be implemented by application event listeners. * @@ -88,7 +88,7 @@ EventPublishingRunListener有什么作用? #### 1.1 EventPublishingRunListener 下面先看下EventPublishingRunListener源码 -``` +```Java /** * SpringApplicationRunListener 是用于发布 SpringApplicationEvent的。 * SpringApplicationRunListener通过内部的ApplicationEventMulticaster在容器刷新之前来触发事件。 @@ -219,7 +219,7 @@ public class EventPublishingRunListener implements SpringApplicationRunListener, ``` EventPublishingRunListener实现了SpringApplicationRunListener接口,该接口定义了用于监听**SpringApplication生命周期**的一系列接口方法。 -``` +```Java public interface SpringApplicationRunListener { /** @@ -283,7 +283,7 @@ public interface SpringApplicationRunListener { ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200623065336310.png) 因此可以知道,在SpringBoot中核心广播器就是SimpleApplicationEventMulticaster,所有管理监听器播放事件的工作都由SimpleApplicationEventMulticaster来完成。下面先来看下接口ApplicationEventMulticaster源码及注释: -``` +```Java /** * ApplicationEventMulticaster接口的实现类用于管理多个ApplicationListener监听器,并对事件进行广播 * @@ -350,7 +350,7 @@ AbstractApplicationEventMulticaster不仅实现了ApplicationEventMulticaster, AbstractApplicationEventMulticaster已经把监听器存储好了,就等着广播器进行事件广播,而广播的方法就是视SimpleApplicationEventMulticaster#multicastEvent方法。 -``` +```Java @Override public void multicastEvent(ApplicationEvent event) { // 广播事件 @@ -378,7 +378,7 @@ AbstractApplicationEventMulticaster已经把监听器存储好了,就等着广 #### 2.1 AbstractApplicationEventMulticaster#getApplicationListeners 既然监听器存放在了播放器里,那么播放器肯定会提供一个获取监听器的方法,那么这个方法就是getApplicationListeners。 **AbstractApplicationEventMulticaster#getApplicationListeners** -``` +```Java protected Collection> getApplicationListeners( ApplicationEvent event, ResolvableType eventType) { @@ -432,11 +432,11 @@ protected Collection> getApplicationListeners( 下图为SpringBoot如何将监听器添加进EventPublishingRunListener中的简易流程图。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200623120430115.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) 除此之外,还有一个重要方法AbstractApplicationEventMulticaster#supportsEvent,该方法有两个重载方法 -``` +```Java supportsEvent( ConfigurableBeanFactory beanFactory, String listenerBeanName, ResolvableType eventType) ``` -``` +```Java supportsEvent(Class listenerType, ResolvableType eventType) ``` @@ -470,13 +470,13 @@ supportsEvent(Class listenerType, ResolvableType eventType) ### 4. 模仿SpringBoot,实现自定义的事件与监听器 首先,定义一个天气事件抽象类 -``` +```Java public abstract class WeatherEvent { public abstract String getWeather(); } ``` 定义两个天气事件 -``` +```Java public class RainEvent extends WeatherEvent{ @Override public String getWeather() { @@ -484,7 +484,7 @@ public class RainEvent extends WeatherEvent{ } } ``` -``` +```Java public class SnowEvent extends WeatherEvent{ @Override public String getWeather() { @@ -494,7 +494,7 @@ public class SnowEvent extends WeatherEvent{ ``` 接着定义一个事件监听器 -``` +```Java public interface WeatherListener { // 类似于SpringBoot监听器的onApplicationEvent方法 void onWeatherEvent(WeatherEvent event); @@ -502,7 +502,7 @@ public interface WeatherListener { ``` 有了监听器接口,那么就要定义实现类 -``` +```Java @Component public class RainListener implements WeatherListener{ @Override @@ -513,7 +513,7 @@ public class RainListener implements WeatherListener{ } } ``` -``` +```Java @Component public class SnowListener implements WeatherListener { @Override @@ -527,7 +527,7 @@ public class SnowListener implements WeatherListener { 可以看到,SnowListener和RainListener类的onWeatherEvent方法会依据对应的天气Event进行过滤。 定义完了监听器以及事件之后,就还差广播器以及调用广播器播放事件的XXRunListener了。先定义一个事件广播器,包含了基础的添加监听器、移除监听器、播放事件的功能。 -``` +```Java public interface EventMulticaster { void multicastEvent(WeatherEvent event); void addListener(WeatherListener weatherListener); @@ -535,7 +535,7 @@ public interface EventMulticaster { } ``` 抽象广播器类 -``` +```Java @Component public abstract class AbstractEventMulticaster implements EventMulticaster{ @@ -569,7 +569,7 @@ public abstract class AbstractEventMulticaster implements EventMulticaster{ ``` 定义完了广播器,就运行广播器的XXRunListener了,下面定义一个WeatherRunListener,用于播放感兴趣的事件。 -``` +```Java @Component public class WeatherRunListener { diff --git "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" index fb79859..868e146 100644 --- "a/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" +++ "b/note/SpringBoot/\346\267\261\345\205\245SpringBoot\346\272\220\347\240\201\345\255\246\344\271\240\344\271\213\342\200\224\342\200\224\347\263\273\347\273\237\345\210\235\345\247\213\345\214\226\345\231\250.md" @@ -1,7 +1,7 @@ ## 前言 前一章已经讲解了SpringBoot的SpringFactoriesLoader类的功能以及作用,即读取spring.factories文件中的工厂类,其中就包括了系统初始化器。在SpringBoot中,系统初始化器名称为ApplicationContextInitializer,它是一个接口,只定义了一个initialize方法。下面将详细介绍下SpringBoot的系统初始化器的原理以及作用,并且自定义一个系统初始化器,并在此基础上讲解下常见的使用场景。 -**SpringBoot版本:5.2.1.RELEASE** +**SpringBoot 版本:2.2.1.RELEASE** ## 正文 @@ -33,13 +33,13 @@ */ ``` - + 注释意思可以总结为以下几点: 1. ApplicationContextInitializer是一个用于在ConfigurableApplicationContext#refresh方法刷新之前,进行回调初始化ConfigurableApplicationContext的一个回调接口。 2. ApplicationContextInitializer通常用于对WEB环境上下文进行编程式地初始化,例如通过ConfigurableApplicationContext#getEnvironment方法获取容器环境来注册属性源以及激活容器配置。 3. ApplicationContextInitializer支持@Order注解,在调用初始化器之前系统会对其进行排序。 -``` +```Java public interface ApplicationContextInitializer { /** @@ -76,7 +76,7 @@ SpringBoot框架已经将spring.factories对应所有的初始化器加载到了 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200608120932643.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center) applyInitializers方法的注释已经注明了方法的作用,即:在SpringIOC容器进行refresh刷新之前,将所有的初始化器应用于SpringIOC容器。传入的context此时还没有进行刷新。 -``` +```Java protected void applyInitializers(ConfigurableApplicationContext context) { // getInitializers()方法会从SpringApplication中获取所有的已实例化的初始化器 for (ApplicationContextInitializer initializer : getInitializers()) { @@ -96,7 +96,7 @@ applyInitializers方法的注释已经注明了方法的作用,即:在Spring ### 2. 自定义ApplicationContextInitializer 自定义一个系统初始化器首先当然得实现ApplicationContextInitializer接口,然后将逻辑写在initialize方法里。 -``` +```Java @Order(1) public class FirstInitializer implements ApplicationContextInitializer { // 下面将在initialize方法中获取ConfigurableEnviroment对象,并自定义一个map存入其中。 @@ -120,7 +120,7 @@ org.springframework.context.ApplicationContextInitializer=com.bruis.learnsb.init ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200608144657719.png#pic_center) 这样,SpringBoot就能识别到FirstInitializer这个自定义初始化器了。下面定义一个service并实现ApplicationContextAware,用于注入ApplicationContext对象。 -``` +```Java @Component public class TestService implements ApplicationContextAware { @@ -144,7 +144,7 @@ public class TestService implements ApplicationContextAware { ``` 然后写一个Test类,测试一下: -``` +```Java @RunWith(SpringRunner.class) @SpringBootTest public class SpringBeanTest { @@ -168,7 +168,7 @@ context.initializer.classes=com.bruis.learnsb.initializer.FirstInitializer ``` 2. SpringApplication#addInitializers -``` +```Java @SpringBootApplication public class LearnsbApplication { diff --git "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 \345\210\235\350\257\206SpringSecurity.md" "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 \345\210\235\350\257\206SpringSecurity.md" index 9f61fff..c757cdc 100644 --- "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 \345\210\235\350\257\206SpringSecurity.md" +++ "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\200\357\274\211\342\200\224\342\200\224 \345\210\235\350\257\206SpringSecurity.md" @@ -79,7 +79,7 @@ SpringBoot集成SpringSecurity需要配置几个配置文件,并且需要几 #### 3.1 @EnableWebSecurity @EnableWebSecurity是Spring Security用于启用Web安全的注解。典型的用法是该注解用在某个Web安全配置类上(实现了接口WebSecurityConfigurer或者继承自WebSecurityConfigurerAdapter)。 -``` +```Java @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @@ -124,7 +124,7 @@ Spring Security默认是禁用注解的,要想开启注解,需要在继承We #### 4.1 认证 在SpringSecurity中,用于认证的主要接口是AuthenticationManager,它只有一个方法: -``` +```Java public interface AuthenticationManager { Authentication authenticate(Authentication authentication) throws AuthenticationException; @@ -135,7 +135,7 @@ AuthenticationManger最普遍的实现类是ProviderManager,而ProviderManager ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200714010510964.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200714010535442.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70) AuthenticationProvider接口和AuthenticationManager相似,但是它有一个额外的方法允许查询它支持的Authentication方式: -``` +```Java public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; @@ -150,7 +150,7 @@ public interface AuthenticationProvider { 一旦认证成功,我们就可以进行授权了,它核心的策略就是AccessDecisionManager。同样的,它将授权逻辑全部委托给AccessDecisionVoter来实现。 一个AccessDecisionVoter考虑一个Authentication(代表一个Principal)和一个被ConfigAttributes装饰的安全对象,这里的ConfigAttributes就是一个包含了URL以及这个URL该有权限的对象的集合。 -``` +```Java boolean supports(ConfigAttribute attribute); boolean supports(Class clazz); diff --git "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224 WebSecurity\345\273\272\351\200\240\346\240\270\345\277\203\351\200\273\350\276\221.md" "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224 WebSecurity\345\273\272\351\200\240\346\240\270\345\277\203\351\200\273\350\276\221.md" index 3d6cfb6..3d88362 100644 --- "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224 WebSecurity\345\273\272\351\200\240\346\240\270\345\277\203\351\200\273\350\276\221.md" +++ "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\270\211\357\274\211\342\200\224\342\200\224 WebSecurity\345\273\272\351\200\240\346\240\270\345\277\203\351\200\273\350\276\221.md" @@ -21,14 +21,14 @@ SpringSecurity通过SecurityConfigurer来建造FilterChainProxy,建造前还 在AbstractConfiguredSecurityBuilder类中,看下安全配置类的定义:‘ -``` +```Java private final LinkedHashMap>, List>> configurers = new LinkedHashMap>, List>>(); ``` 这是定义的安全配置器的子类Map集合,这个configurers就是用于初始化以及配置FilterChainProxy中的filters用的。Map集合中,Key是SecurityConfigurer的子类的Class类型,Value是SecurityConfigurer的list集合。 作为一个成员变量,自然会有方法从外部注入安全配置类。在AbstractConfiguredSecurityBuilder的类中,定义了add方法。 -``` +```Java private > void add(C configurer) throws Exception { Assert.notNull(configurer, "configurer cannot be null"); // 获取安全配置类的Class类型 @@ -64,7 +64,7 @@ private final LinkedHashMap>, List> C apply(C configurer) throws Exception { @@ -82,7 +82,7 @@ private final LinkedHashMap>, List> C getOrApply( C configurer) throws Exception { // 从configurers集合中获取安全配置类 @@ -106,7 +106,7 @@ getOrApply方法主要是从configurers集合中获取配置类,如果存在 找了一圈,发现configure的实现是在ExpressionUrlAuthorizationConfigurer的抽象父类AbstractInterceptUrlConfigurer定义的。 -``` +```Java @Override public void configure(H http) throws Exception { // 创建元数据,该抽象方法由ExpressionUrlAuthorizationConfigurer定义,返回一个ExpressionBasedFilterInvocationSecurityMetadataSource对象 @@ -138,7 +138,7 @@ getOrApply方法主要是从configurers集合中获取配置类,如果存在 ### 2. AbstractConfiguredSecurityBuilder的doBuild()方法 随着configurers集合元素的注入,下面就是进行建造工作,调用doBuild()方法。 -``` +```Java @Override protected final O doBuild() throws Exception { synchronized (configurers) { @@ -173,14 +173,14 @@ getOrApply方法主要是从configurers集合中获取配置类,如果存在 ``` beforeInit()和beforeConfigure()是一个空方法体,没有逻辑。 -``` +```Java protected void beforeInit() throws Exception { } protected void beforeConfigure() throws Exception { } ``` -``` +```Java private void init() throws Exception { // 调用getConfigurers()方法获取this.configurers的所有value值,并以List集合的形式返回 Collection> configurers = getConfigurers(); @@ -201,7 +201,7 @@ beforeInit()和beforeConfigure()是一个空方法体,没有逻辑。 **接着就是configure()方法的调用** -``` +```Java private void configure() throws Exception { // 调用getConfigurers()方法获取this.configurers的所有value值,并以List集合的形式返回 Collection> configurers = getConfigurers(); @@ -217,7 +217,7 @@ beforeInit()和beforeConfigure()是一个空方法体,没有逻辑。 经过init()和configure()方法的执行,以及可以开始进行建造工作了,因而调用performBuild()方法执行建造过程。 -``` +```Java protected abstract O performBuild() throws Exception; ``` 可以看到在AbstractConfiguredSecurityBuilder中,performBuild是以抽象方法的形式存在的,所以实现逻辑都在其子类中。 @@ -242,7 +242,7 @@ WebSecurity用于建造FilterChainProxy,WebSecurity是包含HttpSecurity的一 ### 3. WebSecurity中的performBuild()方法 WebSecurity重写了AbstractConfiguredSecurityBuilder的perfomBuild()方法,核心逻辑如下: -``` +```Java @Override protected Filter performBuild() throws Exception { Assert.state( diff --git "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \345\256\211\345\205\250\350\277\207\346\273\244\345\231\250FilterChainProxy.md" "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \345\256\211\345\205\250\350\277\207\346\273\244\345\231\250FilterChainProxy.md" index f3d7189..2bd7243 100644 --- "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \345\256\211\345\205\250\350\277\207\346\273\244\345\231\250FilterChainProxy.md" +++ "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\344\272\214\357\274\211\342\200\224\342\200\224 \345\256\211\345\205\250\350\277\207\346\273\244\345\231\250FilterChainProxy.md" @@ -10,7 +10,7 @@ - [总结](#总结) - [参考](#参考) - [相关文章](#相关文章) - + ## 前言 相信了解过SpringSecurity或者是OAuth2的读者,会发现网上会有非常多的相关文章,或是纯概念的,或是带有demo的,无论是哪种类型的文章,本人去阅读之后,对于整个框架的概念还是一知半解,也仅仅是实现了某些功能、某些效果而已,若遇到某些问题时无从下手,只能去百度去Google。这是因为对于SpringSecurity和OAuth2的知识没有一个整体概念的把握,知识体系没有形成系统,遂决定写一个关于SpringSecurity和OAuth2的系列专栏,在建造自己知识体系的同时还希望能帮助有同样困惑的同学。 @@ -30,7 +30,7 @@ 首先,WebSecurityConfiguration实现了ImportAware和BeanClassLoaderAware接口,分别实现了setImportMetadata()和setBeanClassLoader() setImportMetadata()方法的作用是注入注解元数据。 -``` +```Java public void setImportMetadata(AnnotationMetadata importMetadata) { // 从注入的importMetadata中获取@EnableWebSecurity注解map值 Map enableWebSecurityAttrMap = importMetadata @@ -47,7 +47,7 @@ setImportMetadata()方法的作用是注入注解元数据。 } ``` setBeanClassLoader方法作用就是注入类加载器ClassLoader。 -``` +```Java public void setBeanClassLoader(ClassLoader classLoader) { this.beanClassLoader = classLoader; } @@ -57,7 +57,7 @@ setBeanClassLoader方法作用就是注入类加载器ClassLoader。 答案就在WebSecurityConfiguration的springSecurityFilterChain()方法中 -``` +```Java // AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME的值是:springSecurityFilterChain // 所以springSecurityFilterChain()的作用就是想Spring容器中注入一个名为springSecurityChain的bean。 @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME) @@ -78,7 +78,7 @@ setBeanClassLoader方法作用就是注入类加载器ClassLoader。 ### 2. WebSecurityConfiguration类 在深入springSecurityFilterChain()方法底层原理之前,需要先了解WebSecurityConfiguration中几个重要的成员变量。 -``` +```Java @Configuration public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware { @@ -111,7 +111,7 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa 我们先来看下WebSecurity的类结构: -``` +```Java public final class WebSecurity extends AbstractConfiguredSecurityBuilder implements SecurityBuilder, ApplicationContextAware { @@ -128,14 +128,14 @@ public final class WebSecurity extends ### 4. AbstractConfiguredSecurityBuilder类 由其类名:AbstractConfiguredSecurityBuilder就可以知道,该类是一个抽象类,作为抽象类,必然会抽象出abstract方法让子类去实现。浏览AbstractConfiguredSecurityBuilder的方法定义,可以看到它内部只定义了一个抽象方法: -``` +```Java protected abstract O performBuild() throws Exception; ``` 这个方法会在建造FilterChainProxy时有使用到,这里先留个印象。 回到AbstractConfiguredSecurityBuilder类定义 -``` +```Java public abstract class AbstractConfiguredSecurityBuilder> extends AbstractSecurityBuilder { // 省略 @@ -147,7 +147,7 @@ public abstract class AbstractConfiguredSecurityBuilder { @@ -155,7 +155,7 @@ public interface SecurityBuilder { } ``` -``` +```Java // 抽象安全建造者 public abstract class AbstractSecurityBuilder implements SecurityBuilder { private AtomicBoolean building = new AtomicBoolean(); @@ -177,7 +177,7 @@ public abstract class AbstractSecurityBuilder implements SecurityBuilder { 所以B extends SecurityBuilder就是指B是SecurityBuilder的子类,用于建造O。 从WebSecurity中的类定义可以发现 -``` +```Java AbstractConfiguredSecurityBuilder ``` @@ -189,7 +189,7 @@ AbstractConfiguredSecurityBuilder作用就是通过WebSecurity这个建造者建 再来看下AbstractConfiguredSecurityBuilder的成员变量。 -``` +```Java public abstract class AbstractConfiguredSecurityBuilder> extends AbstractSecurityBuilder { private final Log logger = LogFactory.getLog(getClass()); @@ -220,7 +220,7 @@ SecurityConfigurer是一个接口,它就是指的安全配置器,看下它 - 安全建造者(SecurityBuilder)在初始化时调用init方法,同时会初始化所有的SecurityConfigurer - 安全建造者(SecurityBuilder)在调用configure方法时,同时会调用所有SecurityConfigurer的configure -``` +```Java public interface SecurityConfigurer> { void init(B builder) throws Exception; @@ -239,7 +239,7 @@ SecurityConfigurer> 这个该怎么理解呢

-``` +```Java private final LinkedHashMap>, List>> configurers = new LinkedHashMap<>(); ``` 所以configurers就是以建造者为key,各种配置类为value的一个LinkedHashMap。 @@ -253,7 +253,7 @@ private final LinkedHashMap>, List objectPostProcessor, @@ -331,7 +331,7 @@ performBuild()方法是AbstractConfiguredSecurityBuilder提供的抽象方法, 方法中的webSecurityConfigurers是通过了@Value注解来注入的bean集合,@Value表达式中又包含了一个autowiredWebSecurityConfigurersIgnoreParents.getWebSecurityConfigurers()的调用。 进入AutowiredWebSecurityConfigurersIgnoreParents类,查看其方法getWebSecurityConfigurers()方法。 -``` +```Java public List> getWebSecurityConfigurers() { // 初始化webSecurityConfigurers集合 List> webSecurityConfigurers = new ArrayList<>(); diff --git "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\345\233\233\357\274\211\342\200\224\342\200\224 FilterChainProxy\350\277\207\346\273\244\345\231\250\351\223\276\344\270\255\347\232\204\345\207\240\344\270\252\351\207\215\350\246\201\347\232\204\350\277\207\346\273\244\345\231\250.md" "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\345\233\233\357\274\211\342\200\224\342\200\224 FilterChainProxy\350\277\207\346\273\244\345\231\250\351\223\276\344\270\255\347\232\204\345\207\240\344\270\252\351\207\215\350\246\201\347\232\204\350\277\207\346\273\244\345\231\250.md" index dc1387b..fb4c0b5 100644 --- "a/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\345\233\233\357\274\211\342\200\224\342\200\224 FilterChainProxy\350\277\207\346\273\244\345\231\250\351\223\276\344\270\255\347\232\204\345\207\240\344\270\252\351\207\215\350\246\201\347\232\204\350\277\207\346\273\244\345\231\250.md" +++ "b/note/SpringSecurity/\344\273\216\351\233\266\345\274\200\345\247\213\347\263\273\347\273\237\345\255\246\344\271\240SpringSecurity\345\222\214OAuth2\357\274\210\345\233\233\357\274\211\342\200\224\342\200\224 FilterChainProxy\350\277\207\346\273\244\345\231\250\351\223\276\344\270\255\347\232\204\345\207\240\344\270\252\351\207\215\350\246\201\347\232\204\350\277\207\346\273\244\345\231\250.md" @@ -29,7 +29,7 @@ #### 1.1 源码分析 -``` +```Java public class SecurityContextPersistenceFilter extends GenericFilterBean { static final String FILTER_APPLIED = "__spring_security_scpf_applied"; @@ -125,7 +125,7 @@ public class SecurityContextPersistenceFilter extends GenericFilterBean { #### 2.1 源码分析 先判断看下ExceptionTranslationFilter的成员变量 -``` +```Java public class ExceptionTranslationFilter extends GenericFilterBean { // AccessDeniedException处理器 @@ -164,7 +164,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { #### 2.2 doFilter源码分析 -``` +```Java public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; @@ -216,7 +216,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { ``` -``` +```Java // SpringSecurityException异常处理的核心逻辑 private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception) @@ -261,7 +261,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { } ``` -``` +```Java protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException { @@ -286,7 +286,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { 进入其configure方法查看 -``` +```Java @Override public void configure(H http) throws Exception { // 获取authenticationEntryPoint @@ -307,7 +307,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { 下面接着查看一下getAuthenticationEntryPoint()方法 -``` +```Java AuthenticationEntryPoint getAuthenticationEntryPoint(H http) { AuthenticationEntryPoint entryPoint = this.authenticationEntryPoint; // 由于entryPoint为空,所以调用createDefaultEntryPoint去创建entryPoint @@ -318,7 +318,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { } ``` -``` +```Java private AuthenticationEntryPoint createDefaultEntryPoint(H http) { // 如果entryPointMappings为空,则返回Http403ForbiddenEntryPoint if (this.defaultEntryPointMappings.isEmpty()) { @@ -341,7 +341,7 @@ public class ExceptionTranslationFilter extends GenericFilterBean { ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200819101823377.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NvZGVyQnJ1aXM=,size_16,color_FFFFFF,t_70#pic_center) 可以看到,HTTP403ForbiddenEntryPiont这个类代码非常少 -``` +```Java public class Http403ForbiddenEntryPoint implements AuthenticationEntryPoint { private static final Log logger = LogFactory.getLog(Http403ForbiddenEntryPoint.class); @@ -361,7 +361,7 @@ public class Http403ForbiddenEntryPoint implements AuthenticationEntryPoint { 这里,还讲解一下另外一个类LoginURLAuthenticationEntryPoint的方法commence。 -``` +```Java public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { // 重定向url diff --git a/note/images/Dubbo/Dubbo-SPI-Test.png b/note/images/Dubbo/Dubbo-SPI-Test.png new file mode 100644 index 0000000..e2b80f5 Binary files /dev/null and b/note/images/Dubbo/Dubbo-SPI-Test.png differ diff --git a/note/images/Dubbo/dubbo-spi-01.png b/note/images/Dubbo/dubbo-spi-01.png new file mode 100644 index 0000000..ead3397 Binary files /dev/null and b/note/images/Dubbo/dubbo-spi-01.png differ diff --git a/note/images/Dubbo/dubbo-spi-02.png b/note/images/Dubbo/dubbo-spi-02.png new file mode 100644 index 0000000..1a01b6e Binary files /dev/null and b/note/images/Dubbo/dubbo-spi-02.png differ diff --git a/note/images/Dubbo/dubbo-spi-03.png b/note/images/Dubbo/dubbo-spi-03.png new file mode 100644 index 0000000..8b2e321 Binary files /dev/null and b/note/images/Dubbo/dubbo-spi-03.png differ diff --git a/note/images/Dubbo/dubbo-spi-04.png b/note/images/Dubbo/dubbo-spi-04.png new file mode 100644 index 0000000..48572f1 Binary files /dev/null and b/note/images/Dubbo/dubbo-spi-04.png differ diff --git a/note/images/Dubbo/export01.png b/note/images/Dubbo/export01.png new file mode 100644 index 0000000..ea3fa24 Binary files /dev/null and b/note/images/Dubbo/export01.png differ diff --git a/note/images/Dubbo/export02.png b/note/images/Dubbo/export02.png new file mode 100644 index 0000000..609ece2 Binary files /dev/null and b/note/images/Dubbo/export02.png differ diff --git a/note/images/Dubbo/jdk-spi-driver.png b/note/images/Dubbo/jdk-spi-driver.png new file mode 100644 index 0000000..1d5841b Binary files /dev/null and b/note/images/Dubbo/jdk-spi-driver.png differ diff --git a/note/images/Dubbo/jdk-spi.png b/note/images/Dubbo/jdk-spi.png new file mode 100644 index 0000000..064f401 Binary files /dev/null and b/note/images/Dubbo/jdk-spi.png differ diff --git a/note/images/Dubbo/spi_@Adaptive.png b/note/images/Dubbo/spi_@Adaptive.png new file mode 100644 index 0000000..f5266fa Binary files /dev/null and b/note/images/Dubbo/spi_@Adaptive.png differ diff --git a/note/images/JDK/volatile-01.png b/note/images/JDK/volatile-01.png new file mode 100644 index 0000000..61eb672 Binary files /dev/null and b/note/images/JDK/volatile-01.png differ diff --git a/note/images/JDK/volatile-02.png b/note/images/JDK/volatile-02.png new file mode 100644 index 0000000..a288136 Binary files /dev/null and b/note/images/JDK/volatile-02.png differ diff --git a/note/images/JDK/volatile-03.png b/note/images/JDK/volatile-03.png new file mode 100644 index 0000000..7ace8b9 Binary files /dev/null and b/note/images/JDK/volatile-03.png differ diff --git a/note/images/JDK/volatile-04.png b/note/images/JDK/volatile-04.png new file mode 100644 index 0000000..44b287a Binary files /dev/null and b/note/images/JDK/volatile-04.png differ diff --git a/note/images/spring/spring-01.png b/note/images/spring/spring-01.png new file mode 100644 index 0000000..26d87c9 Binary files /dev/null and b/note/images/spring/spring-01.png differ diff --git a/note/images/spring/spring-02.png b/note/images/spring/spring-02.png new file mode 100644 index 0000000..e309335 Binary files /dev/null and b/note/images/spring/spring-02.png differ diff --git a/note/images/spring/spring-03.jpg b/note/images/spring/spring-03.jpg new file mode 100644 index 0000000..83952d4 Binary files /dev/null and b/note/images/spring/spring-03.jpg differ diff --git a/note/images/spring/spring-04.jpg b/note/images/spring/spring-04.jpg new file mode 100644 index 0000000..654d4ec Binary files /dev/null and b/note/images/spring/spring-04.jpg differ diff --git a/note/images/spring/spring-05.png b/note/images/spring/spring-05.png new file mode 100644 index 0000000..6191186 Binary files /dev/null and b/note/images/spring/spring-05.png differ diff --git a/note/images/spring/spring-06.png b/note/images/spring/spring-06.png new file mode 100644 index 0000000..f6912dc Binary files /dev/null and b/note/images/spring/spring-06.png differ diff --git a/note/images/spring/spring-07.jpg b/note/images/spring/spring-07.jpg new file mode 100644 index 0000000..cf74eaf Binary files /dev/null and b/note/images/spring/spring-07.jpg differ diff --git a/note/images/spring/spring-08.png b/note/images/spring/spring-08.png new file mode 100644 index 0000000..4f12e5f Binary files /dev/null and b/note/images/spring/spring-08.png differ diff --git a/note/images/spring/spring-09.png b/note/images/spring/spring-09.png new file mode 100644 index 0000000..baff012 Binary files /dev/null and b/note/images/spring/spring-09.png differ diff --git a/note/images/spring/spring-10.png b/note/images/spring/spring-10.png new file mode 100644 index 0000000..6191186 Binary files /dev/null and b/note/images/spring/spring-10.png differ diff --git a/note/images/spring/spring-11.png b/note/images/spring/spring-11.png new file mode 100644 index 0000000..f6912dc Binary files /dev/null and b/note/images/spring/spring-11.png differ diff --git a/note/images/spring/spring-12.jpg b/note/images/spring/spring-12.jpg new file mode 100644 index 0000000..ee53f53 Binary files /dev/null and b/note/images/spring/spring-12.jpg differ diff --git a/note/images/spring/spring-13.png b/note/images/spring/spring-13.png new file mode 100644 index 0000000..5f32cb1 Binary files /dev/null and b/note/images/spring/spring-13.png differ diff --git a/note/images/spring/spring-14.jpg b/note/images/spring/spring-14.jpg new file mode 100644 index 0000000..fe86220 Binary files /dev/null and b/note/images/spring/spring-14.jpg differ diff --git a/note/images/spring/spring-15.png b/note/images/spring/spring-15.png new file mode 100644 index 0000000..1309372 Binary files /dev/null and b/note/images/spring/spring-15.png differ diff --git a/rocketmqdemo/README.md b/rocketmqdemo/README.md new file mode 100644 index 0000000..8c82e18 --- /dev/null +++ b/rocketmqdemo/README.md @@ -0,0 +1,9 @@ +## RocketMQDemo 说明 + +- demo01 普通生产者消费者 + +- demo02 普通生产者消费者(异步发送消息) + +- demo03 顺序消费 + +- demo04 延时消息 \ No newline at end of file diff --git a/rocketmqdemo/pom.xml b/rocketmqdemo/pom.xml new file mode 100644 index 0000000..1b307fd --- /dev/null +++ b/rocketmqdemo/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.1.RELEASE + + + com.bruis + rocketmqdemo + 0.0.1-SNAPSHOT + rocketmqdemo + rocketmqdemo + + 1.8 + 4.9.0 + + + + org.springframework.boot + spring-boot-starter + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.rocketmq + rocketmq-client + ${rocketmq.version} + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/RocketmqdemoApplication.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/RocketmqdemoApplication.java new file mode 100644 index 0000000..f187f13 --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/RocketmqdemoApplication.java @@ -0,0 +1,13 @@ +package com.bruis.rocketmqdemo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RocketmqdemoApplication { + + public static void main(String[] args) { + SpringApplication.run(RocketmqdemoApplication.class, args); + } + +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Consumer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Consumer.java new file mode 100644 index 0000000..554c16a --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Consumer.java @@ -0,0 +1,67 @@ +package com.bruis.rocketmqdemo.demo01; + +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.remoting.common.RemotingHelper; + +import java.util.List; + +/** + * @author lhy + * + * 普通消费者 + * + * @date 2021/7/10 + */ +public class Consumer { + + public static final String DEMO01_CONSUMER_GROUP_NAME = "demo01_consumer_group_name"; + + public static void main(String[] args) throws Exception { + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(DEMO01_CONSUMER_GROUP_NAME); + consumer.setNamesrvAddr(Producer.NAMESRV_ADDRESS); + // 从哪开始进行消费 + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + consumer.subscribe(Producer.TOPIC_NAME,"*"); + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + MessageExt messageExt = msgs.get(0); + try { + String topic = messageExt.getTopic(); + String tags = messageExt.getTags(); + String keys = messageExt.getKeys(); + + if ("keyDuplicate".equals(keys)) { + System.err.println("消息消费失败"); + int a = 1 / 0; + } + + String msgBody = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET); + System.err.println("topic: " + topic + ",tags: " + tags + ", keys: " + keys + ",body: " + msgBody); + } catch (Exception e) { + e.printStackTrace(); + // 消费次数 +// int reconsumeTimes = messageExt.getReconsumeTimes(); +// System.err.println("reconsumeTimes: " + reconsumeTimes); +// // 重试三次 +// if (reconsumeTimes == 3) { +// // 日志记录 +// // 重试补偿成功 +// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; +// } + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + + consumer.start(); + System.err.println("Consumer start ...."); + } +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Producer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Producer.java new file mode 100644 index 0000000..b1c533f --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo01/Producer.java @@ -0,0 +1,45 @@ +package com.bruis.rocketmqdemo.demo01; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.message.Message; + +/** + * @author lhy + * + * 普通生产者 + * + * @date 2021/7/10 + */ +public class Producer { + + public static final String NAMESRV_ADDRESS = "127.0.0.1:9876"; + + // 生产者组 + public static final String DEMO01_PRODUCER_GROUP_NAME = "demo01_producer_group_name"; + + // topic + public static final String TOPIC_NAME = "demo01_topic"; + + public static void main(String[] args) throws Exception { + // 指定生产者组名称 + DefaultMQProducer producer = new DefaultMQProducer(DEMO01_PRODUCER_GROUP_NAME); + producer.setNamesrvAddr(NAMESRV_ADDRESS); + + producer.start(); + + for (int i = 0; i < 5; i++) { + Message message = new Message(TOPIC_NAME,// topic + "TagA",//tag + "key" + i,//keys + ("Hello world RocketMQ Demo01" + i).getBytes()); + + // 向broker发送消息============================> 同步发送 + SendResult sendResult = producer.send(message); + System.out.printf("%s%n", sendResult); + } + + producer.shutdown(); + } + +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Consumer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Consumer.java new file mode 100644 index 0000000..e1f74ae --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Consumer.java @@ -0,0 +1,67 @@ +package com.bruis.rocketmqdemo.demo02; + +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; +import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageExt; +import org.apache.rocketmq.remoting.common.RemotingHelper; + +import java.util.List; + +/** + * @author lhy + * + * 普通消费者 + * + * @date 2021/7/10 + */ +public class Consumer { + + public static final String DEMO01_CONSUMER_GROUP_NAME = "demo02_consumer_group_name"; + + public static void main(String[] args) throws Exception { + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(DEMO01_CONSUMER_GROUP_NAME); + consumer.setNamesrvAddr(Producer.NAMESRV_ADDRESS); + // 从哪开始进行消费 + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + consumer.subscribe(Producer.TOPIC_NAME,"*"); + consumer.registerMessageListener(new MessageListenerConcurrently() { + @Override + public ConsumeConcurrentlyStatus consumeMessage(List msgs, ConsumeConcurrentlyContext context) { + MessageExt messageExt = msgs.get(0); + try { + String topic = messageExt.getTopic(); + String tags = messageExt.getTags(); + String keys = messageExt.getKeys(); + + if ("keyDuplicate".equals(keys)) { + System.err.println("消息消费失败"); + int a = 1 / 0; + } + + String msgBody = new String(messageExt.getBody(), RemotingHelper.DEFAULT_CHARSET); + System.err.println("topic: " + topic + ",tags: " + tags + ", keys: " + keys + ",body: " + msgBody); + } catch (Exception e) { + e.printStackTrace(); + // 消费次数 +// int reconsumeTimes = messageExt.getReconsumeTimes(); +// System.err.println("reconsumeTimes: " + reconsumeTimes); +// // 重试三次 +// if (reconsumeTimes == 3) { +// // 日志记录 +// // 重试补偿成功 +// return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; +// } + return ConsumeConcurrentlyStatus.RECONSUME_LATER; + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + + consumer.start(); + System.err.println("Consumer start ...."); + } +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Producer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Producer.java new file mode 100644 index 0000000..1163545 --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo02/Producer.java @@ -0,0 +1,54 @@ +package com.bruis.rocketmqdemo.demo02; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.SendCallback; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.message.Message; + +/** + * @author lhy + * + * 普通生产者(异步方式发送) + * + * @date 2021/7/10 + */ +public class Producer { + + public static final String NAMESRV_ADDRESS = "127.0.0.1:9876"; + + // 生产者组 + public static final String DEMO01_PRODUCER_GROUP_NAME = "demo02_producer_group_name"; + + // topic + public static final String TOPIC_NAME = "demo02_topic"; + + public static void main(String[] args) throws Exception { + // 指定生产者组名称 + DefaultMQProducer producer = new DefaultMQProducer(DEMO01_PRODUCER_GROUP_NAME); + producer.setNamesrvAddr(NAMESRV_ADDRESS); + + producer.start(); + + for (int i = 0; i < 5; i++) { + Message message = new Message(TOPIC_NAME,// topic + "TagA",//tag + "key" + i,//keys + ("Hello world RocketMQ Demo01" + i).getBytes()); + + // 向broker发送消息 + producer.send(message, new SendCallback() { + @Override + public void onSuccess(SendResult sendResult) { + System.out.printf("msgId: " + sendResult.getMsgId() + ", status: " + sendResult.getSendStatus()); + } + + @Override + public void onException(Throwable e) { + e.printStackTrace(); + System.err.println("==============发送失败"); + } + }); + } + } + +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Consumer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Consumer.java new file mode 100644 index 0000000..951e693 --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Consumer.java @@ -0,0 +1,61 @@ +package com.bruis.rocketmqdemo.demo03; + +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyContext; +import org.apache.rocketmq.client.consumer.listener.ConsumeOrderlyStatus; +import org.apache.rocketmq.client.consumer.listener.MessageListenerOrderly; +import org.apache.rocketmq.common.consumer.ConsumeFromWhere; +import org.apache.rocketmq.common.message.MessageExt; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * 顺序消息消费,带事务方式(应用可控制Offset什么时候提交) + * + * @author lhy + * @date 2021/7/23 + */ +public class Consumer { + + public static void main(String[] args) throws Exception { + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(Producer.GROUP_NAME); + consumer.setNamesrvAddr(Producer.NAMESRV_ADDRESS); + + /** + * 设置Consumer第一次启动是从队列头部开始消费还是队列尾部开始消费 + * 如果非第一次启动,那么按照上次消费的位置继续消费 + */ + consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_LAST_OFFSET); + consumer.subscribe(Producer.TOPIC_NAME, "TagA || TagC || TagD"); + + /** + * 顺序消费的消息类??MessageListenerOrderly + */ + consumer.registerMessageListener(new MessageListenerOrderly() { + + Random random = new Random(); + + @Override + public ConsumeOrderlyStatus consumeMessage(List msgs, ConsumeOrderlyContext context) { + context.setAutoCommit(true); + for (MessageExt msg : msgs) { + // 可以看到每个queue有唯一的consume线程来消费, 订单对每个queue(分区)有序 + System.out.println("consumeThread=" + Thread.currentThread().getName() + "queueId=" + msg.getQueueId() + ", content:" + new String(msg.getBody())); + } + + try { + //模拟业务逻辑处理中... + TimeUnit.SECONDS.sleep(random.nextInt(10)); + } catch (Exception e) { + e.printStackTrace(); + } + return ConsumeOrderlyStatus.SUCCESS; + } + }); + + consumer.start(); + System.out.println("Consumer Started."); + } +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Producer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Producer.java new file mode 100644 index 0000000..d2af5df --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo03/Producer.java @@ -0,0 +1,162 @@ +package com.bruis.rocketmqdemo.demo03; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.client.producer.MessageQueueSelector; +import org.apache.rocketmq.client.producer.SendResult; +import org.apache.rocketmq.common.message.Message; +import org.apache.rocketmq.common.message.MessageQueue; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +/** + * + * 顺序消息样例 + * + * @author lhy + * @date 2021/7/23 + */ +public class Producer { + + public static final String NAMESRV_ADDRESS = "127.0.0.1:9876"; + + public static final String GROUP_NAME = "inorder_group_name"; + + public static final String TOPIC_NAME = "inorder_topic"; + + public static void main(String[] args) throws Exception { + DefaultMQProducer producer = new DefaultMQProducer(GROUP_NAME); + producer.setNamesrvAddr(NAMESRV_ADDRESS); + + producer.start(); + + String[] tags = {"TagA", "TagC", "TagD"}; + + // 订单列表 + List orderList = new Producer().buildOrders(); + Date date = new Date(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateStr = sdf.format(date); + for (int i = 0; i < 10; i++) { + // 时间前缀 + String body = dateStr + " Hello RocketMQ " + orderList.get(i); + Message msg = new Message(TOPIC_NAME, tags[i % tags.length], "KEY" + i, body.getBytes()); + +// SendResult sendResult = producer.send(msg, (mqs, msg1, arg) -> { +// Long id = (Long) arg; //根据订单id选择发送queue +// long index = id % mqs.size(); +// return mqs.get((int) index); +// }, orderList.get(i).getOrderId());//订单id + + SendResult sendResult = producer.send(msg, new MessageQueueSelector() { + @Override + public MessageQueue select(List mqs, Message msg, Object arg) { + Long id = (Long) arg; + long index = id % mqs.size(); + return mqs.get((int) index); + } + }, orderList.get(i).getOrderId()); + + System.out.println(String.format("SendResult status:%s, queueId:%d, body:%s", + sendResult.getSendStatus(), + sendResult.getMessageQueue().getQueueId(), + body)); + } + + producer.shutdown(); + } + + /** + * 订单的步骤 + */ + private class OrderStep { + private long orderId; + private String desc; + + public long getOrderId() { + return orderId; + } + + public void setOrderId(long orderId) { + this.orderId = orderId; + } + + public String getDesc() { + return desc; + } + + public void setDesc(String desc) { + this.desc = desc; + } + + @Override + public String toString() { + return "OrderStep{" + + "orderId=" + orderId + + ", desc='" + desc + '\'' + + '}'; + } + } + + /** + * 生成模拟订单数据 + */ + private List buildOrders() { + List orderList = new ArrayList(); + + OrderStep orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111039L); + orderDemo.setDesc("创建"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111065L); + orderDemo.setDesc("创建"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111039L); + orderDemo.setDesc("付款"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103117235L); + orderDemo.setDesc("创建"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111065L); + orderDemo.setDesc("付款"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103117235L); + orderDemo.setDesc("付款"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111065L); + orderDemo.setDesc("完成"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111039L); + orderDemo.setDesc("推送"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103117235L); + orderDemo.setDesc("完成"); + orderList.add(orderDemo); + + orderDemo = new OrderStep(); + orderDemo.setOrderId(15103111039L); + orderDemo.setDesc("完成"); + orderList.add(orderDemo); + + return orderList; + } + +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageConsumer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageConsumer.java new file mode 100644 index 0000000..97b85be --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageConsumer.java @@ -0,0 +1,39 @@ +package com.bruis.rocketmqdemo.demo04; + +import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; +import org.apache.rocketmq.client.consumer.listener.*; +import org.apache.rocketmq.common.message.MessageExt; +import java.util.List; + +/** + * + * 延时消息消费者 + * + * @author lhy + * @date 2021/7/24 + */ +public class ScheduledMessageConsumer { + + public static void main(String[] args) throws Exception { + + DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(ScheduledMessageProducer.GROUP_NAME); + // 订阅指定的topic + consumer.subscribe(ScheduledMessageProducer.TOPIC_NAME, "*"); + consumer.setNamesrvAddr(ScheduledMessageProducer.NAMESRV_ADDRESS); + // 注册消息监听者 + consumer.registerMessageListener(new MessageListenerConcurrently() { + + // 消费消息 + @Override + public ConsumeConcurrentlyStatus consumeMessage(List messages, ConsumeConcurrentlyContext context) { + for (MessageExt message : messages) { + // Print approximate delay time period + System.out.println("Receive message[msgId=" + message.getMsgId() + "] " + (System.currentTimeMillis() - message.getBornTimestamp()) + "ms later"); + } + return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; + } + }); + consumer.start(); + } + +} diff --git a/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageProducer.java b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageProducer.java new file mode 100644 index 0000000..356f370 --- /dev/null +++ b/rocketmqdemo/src/main/java/com/bruis/rocketmqdemo/demo04/ScheduledMessageProducer.java @@ -0,0 +1,40 @@ +package com.bruis.rocketmqdemo.demo04; + +import org.apache.rocketmq.client.producer.DefaultMQProducer; +import org.apache.rocketmq.common.message.Message; + +/** + * + * 延时消息生产 + * + * @author lhy + * @date 2021/7/24 + */ +public class ScheduledMessageProducer { + + public static final String GROUP_NAME = "scheduled_group"; + + public static final String TOPIC_NAME = "scheduled_test_topic"; + + public static final String NAMESRV_ADDRESS = "127.0.0.1:9876"; + + public static void main(String[] args) throws Exception { + + DefaultMQProducer producer = new DefaultMQProducer(GROUP_NAME); + producer.setNamesrvAddr(NAMESRV_ADDRESS); + producer.start(); + + int totalMessagesToSend = 100; + + for (int i = 0; i < totalMessagesToSend; i++) { + Message message = new Message(TOPIC_NAME, ("Hello Scheduled Message " + i).getBytes()); + // 设置延时等级3,这个消息将在10s之后发送(现在只支持固定的几个时间,详看delayTimeLevel) + message.setDelayTimeLevel(3); + // 发送消息 + producer.send(message); + } + + producer.shutdown(); + } + +} diff --git a/rocketmqdemo/src/main/resources/application.properties b/rocketmqdemo/src/main/resources/application.properties new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/rocketmqdemo/src/main/resources/application.properties @@ -0,0 +1 @@ + diff --git a/rocketmqdemo/src/test/java/com/bruis/rocketmqdemo/RocketmqdemoApplicationTests.java b/rocketmqdemo/src/test/java/com/bruis/rocketmqdemo/RocketmqdemoApplicationTests.java new file mode 100644 index 0000000..f1c73b4 --- /dev/null +++ b/rocketmqdemo/src/test/java/com/bruis/rocketmqdemo/RocketmqdemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.bruis.rocketmqdemo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class RocketmqdemoApplicationTests { + + @Test + void contextLoads() { + } + +}