基于Netty手写一个远程连接Redis的IDEA插件
前言
前几天一直在学习Netty框架,写了几个Demo,然后就想着可以用它来写点什么,然后又对编写idea的插件有点兴趣,那么就准备写一个idea插件.
写什么好呢,想起可以写一个Redis连接客户端的插件,这个也可以用上Netty,虽然市面上已经有很多redis的客户端,例如:Redis Desktop Manager这类的,不过很多是付费的,想白嫖需要找破解版,自己写的功能上虽然简陋,不过胜在使用方便,不用另开程序.很多时候也仅仅是想看看redis里数据有没有保存上,所以也够用了.
想要完成这个插件 需要掌握Netty和java Gui的一些知识,如果你完全不了解的话可以先看一下这方面的内容
创建项目
可以看到idea可以直接选择创建插件项目
生成的项目结构
里面会有一个 plugin.xml文件,这个是插件的一个重要配置文件
src 下编写代码
设计ui界面
我们要写一个侧边的工具窗口,那么就需要界面布局,idea里使用 swingUi
按照图上选择 就会生成一个ui编辑器
只需从右侧拖拽到中间的框内就可以完成ui布局,你完成的布局他会同时为你生成一个对应的实体类 你想为哪个组件生成实体类中的字段就要在 field name 这里指定字段名称
最终会生成如下图的一个实体类
然后就可以在实体类中编写业务代码了
核心代码
话不多说,先上代码
public class RedisCliUi {
//这里都是生成的组件字段
private JButton connectButton;
private JTextField portText;
private JTextField commandText;
private JButton commandButton;
private JTextField ipText;
private JTextArea textArea;
private JLabel ipLabel;
private JLabel portLabel;
private JLabel commandLabel;
private JPanel redisPanel;
private JButton cleanButton;
private JButton closeButton;
private JScrollPane scrollPane;
static String line = "\r\n";
static ChannelHandlerContext Context;
//实体类构造
public RedisCliUi(Project project, ToolWindow toolWindow) {
// 连接按钮监听
connectButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (StringUtils.isNotBlank(ipText.getText()) && StringUtils.isNotBlank(portText.getText())) {
new Thread(() -> {
connect(ipText.getText().trim(), portText.getText().trim());
}).start();
}
}
});
// 命令按钮监听
commandButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Context.writeAndFlush(getRedisMessage(Context, commandText.getText()));
}
});
// 清空按钮监听
cleanButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
textArea.setText("");
}
});
// 关闭连接按钮监听
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Context.close();
}
});
// 文本框回车事件监听
textArea.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if ((char) e.getKeyChar() == KeyEvent.VK_ENTER) {
String text = textArea.getText();
System.out.println(text);
String[] split = text.split("\n");
String s = split[split.length - 1];
System.out.println(s);
Context.writeAndFlush(getRedisMessage(Context, s));
}
}
});
// 端口号输入框回车事件监听
portText.addKeyListener(new KeyAdapter() {
@Override
public void keyTyped(KeyEvent e) {
if ((char) e.getKeyChar() == KeyEvent.VK_ENTER) {
if (StringUtils.isNotBlank(ipText.getText()) && StringUtils.isNotBlank(portText.getText())) {
new Thread(() -> {
connect(ipText.getText().trim(), portText.getText().trim());
}).start();
}
}
}
});
}
// 整个ui最外层的panel提供get方法
public JPanel getRedisPanel() {
return redisPanel;
}
// 通过netty客户端连接Redis方法
// 这里是netty的客户端代码
public void connect(String ip, String port) {
NioEventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// pipeline.addLast(new LoggingHandler(LogLevel.INFO));
pipeline.addLast(new RedisDecoder());
pipeline.addLast(new RedisBulkStringAggregator());
pipeline.addLast(new RedisArrayAggregator());
pipeline.addLast(new RedisEncoder());
pipeline.addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接redis 成功");
textArea.append("连接redis 成功");
textArea.append(line);
Context = ctx;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
RedisMessage redisMessage = (RedisMessage) msg;
// 打印响应消息
printAggregatedRedisResponse(redisMessage);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx.close();
textArea.append("连接已关闭");
textAreaFocus(textArea, line);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
textArea.append("连接出现异常已关闭");
textAreaFocus(textArea, line);
}
});
}
});
try {
ChannelFuture channelFuture = bootstrap.connect(ip, Integer.parseInt(port)).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
textArea.append("Redis连接 失败,请输入正确的ip与端口号");
textAreaFocus(textArea, line);
} finally {
group.shutdownGracefully();
}
}
// 处理redis返回数据
private void printAggregatedRedisResponse(RedisMessage msg) {
if (msg instanceof SimpleStringRedisMessage) {
System.out.println(((SimpleStringRedisMessage) msg).content());
textArea.append(((SimpleStringRedisMessage) msg).content());
textAreaFocus(textArea, line);
} else if (msg instanceof ErrorRedisMessage) {
System.out.println(((ErrorRedisMessage) msg).content());
textArea.append(((ErrorRedisMessage) msg).content());
textAreaFocus(textArea, line);
} else if (msg instanceof IntegerRedisMessage) {
System.out.println(((IntegerRedisMessage) msg).value());
textArea.append(String.valueOf(((IntegerRedisMessage) msg).value()));
textAreaFocus(textArea, line);
} else if (msg instanceof FullBulkStringRedisMessage) {
System.out.println(getString((FullBulkStringRedisMessage) msg));
textArea.append(getString((FullBulkStringRedisMessage) msg));
textAreaFocus(textArea, line);
} else if (msg instanceof ArrayRedisMessage) {
for (RedisMessage child : ((ArrayRedisMessage) msg).children()) {
printAggregatedRedisResponse(child);
}
} else {
throw new CodecException("unknown message type: " + msg + "\r\n");
}
}
private static String getString(FullBulkStringRedisMessage msg) {
if (msg.isNull()) {
return "(null)";
}
return msg.content().toString(CharsetUtil.UTF_8);
}
// 处理文本框光标位置在最后一行
public void textAreaFocus(JTextArea textArea, String line) {
textArea.append(line);
textArea.selectAll();
textArea.setCaretPosition(textArea.getSelectedText().length());
textArea.requestFocus();
}
// 将字符串处理成redis可以读取的消息
public static RedisMessage getRedisMessage(ChannelHandlerContext ctx, String str) {
// 匹配字符中空格分隔
String[] commands = str.split("\s+");
List<RedisMessage> children = new ArrayList<>(commands.length);
for (String cmdString : commands) {
children.add(new FullBulkStringRedisMessage(ByteBufUtil.writeUtf8(ctx.alloc(), cmdString)));
}
return new ArrayRedisMessage(children);
}
}
上面代码的逻辑就是实体类开始构造会把事件监听器加载进去,通过connect()方法把输入的ip与端口号传入进去,通过netty与redis建立连接
channelRead()方法读取redis返回的数据
通过发送命令的按钮监听 和文本域的回车事件监听,给redis发送消息
生成窗口
要想生成窗口还需要实现 ToolWindowFactory接口的 createToolWindowContent方法
public class RedisClientFactory implements ToolWindowFactory {
// 生成右侧工具窗口
@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
RedisCliUi redisClientUi = new RedisCliUi(project,toolWindow);
// 获取内容工厂的实例
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
// 获取 ToolWindow 显示的内容
Content content = contentFactory.createContent(redisClientUi.getRedisPanel(), "", false);
// 设置 ToolWindow 显示的内容
toolWindow.getContentManager().addContent(content);
}
}
redisPanel 是ui最外层的panel给他一个get方法 传给内容工场,他会把ui里所有的内容加载出来
还需要配置 plugin.xml 把我们的窗口配置进去
<extensions defaultExtensionNs="com.intellij">
<!-- Add your extensions here -->
<toolWindow id="RedisClient" secondary="false" anchor="right" factoryClass="window.RedisClientFactory"/>
</extensions>
id 只要不重复就可以 anchor 指定右边,就是工具在右侧边栏固定 factoryClass 填写RedisClientFactory的包路径他会找到生成窗口
点击buiud 按上图 会生成插件的zip包 就可以本地安装插件了
效果
可以在Redis命令文本框发送命令,也可以直接在文本域发送命令 基本使用效果和在linux上使用redis_cli差不多
到此这个插件的开发就完成了,很多地方还不太完美大家见谅.
插件已上传到网盘,有兴趣的可以自己使用一下
作者:徐小白
链接:
https://juejin.cn/post/7050291563498307598