Java GUI——网页浏览器开发
前言:为了做java课设,学了一手Java GUI。感觉蛮有意思的,写写文章,做个视频记录一下。欢迎大家友善指出我的不足
网页浏览器开发录制视频,从头敲到尾
任务需求
界面需求
-  菜单栏 - 文件 【热键F】 
    - 另存为 【热键A,快捷键CTRL+S】
- 退出 【热键I,快捷键CTRL+E】
 
- 编辑 【热键E】 
    - 前进 【快捷键CTRL+Z】
- 后退 【快捷键CTRL+D】
 
- 视图 【热键V】 
    - 全屏 【快捷键CTRL+U】
- 查看源码 【热键C,快捷键CTRL+C】
- 刷新 【快捷键CTRL+R】
 
 
- 文件 【热键F】 
    
-  工具栏 - 另存为
- 后退
- 前进
- 查看源代码
- 退出
 
-  工具栏 - label(地址)
- 文本框
- 转向按钮
 
功能需求
另存为:保存正在访问的界面
前进:访问现有的上一个界面
后退:访问现有页面的下一个页面
查看源文件:查看访问页面的HTML源文件、并提供保存、退出功能
参考样式
 

功能解析
界面搭建
- 界面解析【主界面】
  
tip:
WebView的使用注意事项
需要在FX线程中使用
/** * <p>{@code WebView} objects must be created and accessed solely from the * FX thread. */
Platform.runLater说明
能够启动JavaFX线程
/** * Run the specified Runnable on the JavaFX Application Thread at some * unspecified */
启动前,不能启动FX runtime. 对于Swing,只要初始化第一个JFXPanel,就算启动FX runtime
/** * <p> * This method must not be called before the FX runtime has been * initialized. * For Swing applications that use JFXPanel to display FX content, the FX * runtime is initialized when the first JFXPanel instance is constructed. * For SWT application that use FXCanvas to display FX content, the FX * <p> */WebView无法直接添加到JPanel中,需要借助JFXPanel,同时WebView的创建需要在独立的FX线程,因此WebView添加JPanel的代码稍微有些麻烦
Platform.runLater(() -> { // 不能再Platform.runLater之前运行, 否则就启动了FX runtime, Platform运行会报错 webView = new WebView(); // webPanel 是JFXPanel webPanel.setScene(new Scene(webView)); // 加载网页 webView.getEngine().load(url); }); // TODO 将webPanel添加到JPanel中
- 界面解析【查看源代码界面】 
  
模块划分

URL存储数组,用单独的类(URLList)来维护
URLList,建立起
主界面,html代码界面,监听器界面沟通的桥梁。同时URLList维护的数组,能够存储URL访问历史记录,实现网页前进,后退功能
代码编写
maven坐标
    <dependencies>
        <!--Servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!-- Lombok 依赖 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
            <scope>provided</scope>
        </dependency>
        <!--MyBatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>3.5.2</version>
        </dependency>
        <!--MySQL-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.0</version>
            </plugin>
        </plugins>
    </build>
utils
Constant
全局常量
package com.xhf.keshe.utils;
import java.awt.*;
public interface Constant {
    int MAIN_WIDTH = 1920;
    int MAIN_HEIGHT = 1080;
    String[] menuList1 = {"另存为(A)", "退出(I)"};
    String[] menuList2 = {"前进", "后退"};
    String[] menuList3 = {"全屏", "查看源码(C)", "刷新"};
    Font baseFont = new Font("仿宋", Font.BOLD, 20);
    Font smallFont = new Font("仿宋", Font.BOLD, 15);
    String[] toolBarButtonNameList = {"另存为", "后退", "前进", "查看源码", "退出"};
    int SOURCE_WIDTH = 800;
    int SOURCE_HEIGHT = 600;
}
WebsiteHTMLGetter
通过URL解析出html代码
package com.xhf.keshe.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class WebsiteHTMLGetter {
    public static String getHTMLCode(String url) throws IOException {
        URL website = new URL(url);
        HttpURLConnection connection = (HttpURLConnection) website.openConnection();
        connection.setRequestMethod("GET");
        // 获取网页内容
        BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        StringBuilder htmlCode = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            htmlCode.append(line).append("\n");
        }
        reader.close();
        return htmlCode.toString();
    }
}
URLList
维护URL记录
package com.xhf.keshe.utils;
import java.util.ArrayList;
import java.util.List;
public class URLList {
    private static List<String> queue = new ArrayList<String>();
    private static int pointer = -1;
    // 获取当前元素
    public static String getCur() {
        if (queue.size() == 0) return null;
        return queue.get(pointer);
    }
    // 指针右移
    public static boolean right() {
        if (pointer == queue.size() - 1) {
            return false;
        }
        pointer++;
        return true;
    }
    // 指针左移
    public static boolean left() {
        if (pointer == 0) {
            return false;
        }
        pointer--;
        return true;
    }
    // 添加元素
    public static boolean add(String url) {
        if (pointer + 1 > queue.size() - 1) {
            queue.add(url);
            ++pointer;
        }else {
            queue.set(++pointer, url);
        }
        return true;
    }
}
界面
主界面
package com.xhf.keshe;
import com.xhf.keshe.listener.*;
import com.xhf.keshe.utils.Constant;
import com.xhf.keshe.utils.URLList;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
/**
 * 主界面
 */
public class Main extends JFrame {
    public static JPanel toolBarPanel;
    /**
     * http地址接收栏
     */
    private static JTextField http = new JTextField();
    /**
     * 转向
     */
    private JButton redirect = new JButton("转向");
    /**
     * 显示web界面的panel
     */
    public static JFXPanel webPanel;
    /**
     * 显示website
     */
    public static WebView webView;
    /**
     * 刷新界面
     */
    public static void refreshWebSite(String url) {
        Platform.runLater(() -> {
            webView = new WebView();
            webPanel.setScene(new Scene(webView));
            // 加载网页
            webView.getEngine().load(url);
            // 重新加载http
            http.setText(url);
        });
    }
    public Main() {
        this.setSize(Constant.MAIN_WIDTH, Constant.MAIN_HEIGHT);
        // 居中
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        // 初始化界面
        initUI();
        refreshWebSite(http.getText());
    }
    /**
     * 初始化界面
     */
    private void initUI() {
        // 初始化所有的panel界面
        initPanel();
        // 初始化菜单栏
        initMenu();
        // 初始化工具栏1
        initToolBar1();
        // 初始化工具栏2
        initToolBar2();
    }
    /**
     * 初始化panel界面
     */
    private void initPanel() {
        toolBarPanel = new JPanel();
        toolBarPanel.setLayout(new GridLayout(2, 1));
        webPanel = new JFXPanel();
        webPanel.setLayout(new BorderLayout());
        webPanel.add(toolBarPanel, BorderLayout.NORTH);
        add(webPanel);
    }
    /**
     * 初始化二号工具栏
     */
    private void initToolBar2() {
        JToolBar jToolBar2 = new JToolBar();
        JLabel jLabel = new JLabel("地址");
        jLabel.setFont(Constant.smallFont);
        jToolBar2.add(jLabel);
        http.setFont(Constant.smallFont);
        http.setText("https://www.baidu.com/");
        URLList.add("https://www.baidu.com/");
        jToolBar2.add(http);
        redirect.setFont(Constant.smallFont);
        // 执行网页跳转逻辑
        redirect.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                URLList.add(http.getText());
                refreshWebSite(http.getText());
            }
        });
        jToolBar2.add(redirect);
        toolBarPanel.add(jToolBar2);
    }
    /**
     * 初始化一号工具栏
     */
    private void initToolBar1() {
        JToolBar jToolBar1 = new JToolBar();
        for (int i = 0; i < Constant.toolBarButtonNameList.length; i++) {
            JButton jButton = new JButton(Constant.toolBarButtonNameList[i]);
            jButton.setFont(Constant.smallFont);
            jToolBar1.add(jButton);
            try {
                if (Constant.toolBarButtonNameList[i].equals("另存为")) {
                    jButton.addActionListener(new SaveCodeListener());
                } else if (Constant.toolBarButtonNameList[i].equals("后退")) {
                    jButton.addActionListener(new URLmoveListener(URLmoveListener.BACKEND));
                } else if (Constant.toolBarButtonNameList[i].equals("前进")) {
                    jButton.addActionListener(new URLmoveListener(URLmoveListener.FORWARD));
                } else if (Constant.toolBarButtonNameList[i].equals("查看源码")) {
                    jButton.addActionListener(new GetSourceCodeListener());
                } else {
                    jButton.addActionListener(new QuitListener(this));
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        }
        toolBarPanel.add(jToolBar1);
    }
    /**
     * 初始化菜单栏
     */
    private void initMenu() {
        JMenuBar jMenuBar = new JMenuBar();
        // 初始化 '文件' 菜单
        JMenu jMenu1 = new JMenu("文件(ALT+F)");
        jMenu1.setMnemonic(KeyEvent.VK_F);
        jMenu1.setFont(Constant.baseFont);
        for (int i = 0; i < Constant.menuList1.length; i++) {
            JMenuItem item = new JMenuItem(Constant.menuList1[i]);
            item.setFont(Constant.baseFont);
            // 添加热键, 快捷键
            if (Constant.menuList1[i].equals("另存为(A)")) {
                // A
                item.setMnemonic(KeyEvent.VK_A);
                // ctrl + s
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, ActionEvent.CTRL_MASK));
                item.addActionListener(new SaveCodeListener());
            }else if (Constant.menuList1[i].equals("退出(I)")) {
                // I
                item.setMnemonic(KeyEvent.VK_I);
                // ctrl + e
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, ActionEvent.CTRL_MASK));
                item.addActionListener(new QuitListener(this));
            }
            jMenu1.add(item);
        }
        // 初始化 '编辑' 菜单
        JMenu jMenu2 = new JMenu("编辑(ALT+E)");
        jMenu2.setMnemonic(KeyEvent.VK_E);
        jMenu2.setFont(Constant.baseFont);
        for (int i = 0; i < Constant.menuList2.length; i++) {
            JMenuItem item = new JMenuItem(Constant.menuList2[i]);
            item.setFont(Constant.baseFont);
            // 添加快捷键
            if (Constant.menuList2[i].equals("前进")) {
                // ctrl + z
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, ActionEvent.CTRL_MASK));
                item.addActionListener(new URLmoveListener(URLmoveListener.FORWARD));
            }else if (Constant.menuList2[i].equals("后退")){
                // ctrl + d
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, ActionEvent.CTRL_MASK));
                item.addActionListener(new URLmoveListener(URLmoveListener.BACKEND));
            }
            jMenu2.add(item);
        }
        // 初始化 '视图' 菜单
        JMenu jMenu3 = new JMenu("视图(ALT+V)");
        jMenu3.setMnemonic(KeyEvent.VK_V);
        jMenu3.setFont(Constant.baseFont);
        for (int i = 0; i < Constant.menuList3.length; i++) {
            JMenuItem item = new JMenuItem(Constant.menuList3[i]);
            item.setFont(Constant.baseFont);
            // 添加热键, 快捷键
            if (Constant.menuList3[i].equals("全屏")) {
                // ctrl + u
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_U, ActionEvent.CTRL_MASK));
                item.addActionListener(new FullScreenListener(this));
            }else if (Constant.menuList3[i].equals("查看源码(C)")) {
                // c
                item.setMnemonic(KeyEvent.VK_C);
                // ctrl + c
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
                item.addActionListener(new GetSourceCodeListener());
            }else if (Constant.menuList3[i].equals("刷新")) {
                // ctrl + r
                item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, ActionEvent.CTRL_MASK));
                item.addActionListener(new RefreshWebSite());
            }
            jMenu3.add(item);
        }
        jMenuBar.add(jMenu1);
        jMenuBar.add(jMenu2);
        jMenuBar.add(jMenu3);
        this.setJMenuBar(jMenuBar);
    }
    public static void main(String[] args) {
        Main main = new Main();
    }
}
查看源代码界面
package com.xhf.keshe.source;
import com.xhf.keshe.listener.QuitListener;
import com.xhf.keshe.listener.SaveCodeListener;
import com.xhf.keshe.utils.Constant;
import com.xhf.keshe.utils.URLList;
import com.xhf.keshe.utils.WebsiteHTMLGetter;
import javax.swing.*;
import java.awt.*;
import java.io.*;
public class SourcePage extends JFrame{
    /**
     * 工作界面
     */
    private JPanel workspace = new JPanel();
    public SourcePage() {
        this.setSize(Constant.SOURCE_WIDTH, Constant.SOURCE_HEIGHT);
        // 居中
        this.setLocationRelativeTo(null);
        this.setVisible(true);
        // 初始化界面
        initUI();
    }
    /**
     * 初始化UI
     */
    private void initUI() {
        workspace.setLayout(new BorderLayout());
        // 初始化标题
        JLabel title = new JLabel("源代码");
        title.setFont(Constant.baseFont);
        title.setHorizontalAlignment(SwingConstants.CENTER);
        // 初始化文本域
        initTextArea();
        // 初始化按钮
        initButton();
        add(workspace);
    }
    /**
     * 初始化按钮
     */
    private void initButton() {
        // 添加按钮显示区域
        JPanel buttonPanel = new JPanel();
        buttonPanel.setLayout(new FlowLayout());
        // 创建按钮
        JButton save = new JButton("保存");
        save.setFont(Constant.baseFont);
        save.addActionListener(new SaveCodeListener());
        JButton quit = new JButton("退出");
        quit.addActionListener(new QuitListener(this));
        quit.setFont(Constant.baseFont);
        buttonPanel.add(save);
        buttonPanel.add(quit);
        // 添加到panel中
        workspace.add(buttonPanel, BorderLayout.SOUTH);
    }
    /**
     * 初始化文本域
     */
    private void initTextArea() {
        JTextArea sourceCode = new JTextArea();
        // 设置自动换行
        sourceCode.setLineWrap(true);
        // 设置自动换行时,以单词为单位换行
        sourceCode.setWrapStyleWord(true);
        try {
            String URL = URLList.getCur();
            String htmlCode = WebsiteHTMLGetter.getHTMLCode(URL);
            // 获取源代码
            sourceCode.setText(htmlCode);
        } catch (IOException e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, "Could not read website source code, please try again or check the http is correct");
        }
        JScrollPane jScrollPane = new JScrollPane(sourceCode);
        // 添加源代码显示区域
        workspace.add(jScrollPane, BorderLayout.CENTER);
    }
}
listener
FullScreenListener
监听器:实现窗体全屏功能
package com.xhf.keshe.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class FullScreenListener implements ActionListener {
    private final JFrame fullScreenFrame;
    private boolean isFullScreen = false;
    public FullScreenListener(JFrame fullScreenFrame) {
        this.fullScreenFrame = fullScreenFrame;
    }
    /**
     * Invoked when an action occurs.
     *
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        isFullScreen = !isFullScreen;
        if (isFullScreen) {
            fullScreenFrame.setExtendedState(JFrame.MAXIMIZED_BOTH);
        } else {
            fullScreenFrame.setExtendedState(JFrame.NORMAL);
        }
    }
}
GetSourceCodeListener
监听器:获取查看源代码窗体
package com.xhf.keshe.listener;
import com.xhf.keshe.source.SourcePage;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class GetSourceCodeListener implements ActionListener {
    /**
     * Invoked when an action occurs.
     *
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        // 创建JFrame
        SourcePage sourcePage = new SourcePage();
    }
}
QuitListener
package com.xhf.keshe.listener;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
/**
 * 执行退出逻辑
 */
public class QuitListener implements ActionListener {
    private JFrame jFrame;
    public QuitListener(JFrame frame) {
        this.jFrame = frame;
    }
    /**
     * Invoked when an action occurs.
     *
     * @param e
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        // 点击退出按钮时,关闭当前JFrame
        jFrame.dispose();
    }
}
RefreshWebSite
监听器:刷新网页
package com.xhf.keshe.listener;
import com.xhf.keshe.Main;
import com.xhf.keshe.utils.URLList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RefreshWebSite implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        Main.refreshWebSite(URLList.getCur());
    }
}
SaveCodeListener
监听器:保存源代码
package com.xhf.keshe.listener;
import com.xhf.keshe.utils.URLList;
import com.xhf.keshe.utils.WebsiteHTMLGetter;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
public class SaveCodeListener implements ActionListener {
    @Override
    public void actionPerformed(ActionEvent e) {
        String URL = URLList.getCur();
        String htmlCode = null;
        try {
            htmlCode = WebsiteHTMLGetter.getHTMLCode(URL);
        } catch (IOException ioException) {
            ioException.printStackTrace();
        }
        String fileName = removeUrlPrefix(URL);
        System.out.println(fileName);
        try {
            String path = "E:\\B站视频创作\\Java课设\\网页浏览器开发\\代码\\src\\main\\resources\\sourceCode\\";
            // 写文件
            writeFile(path + fileName, htmlCode);
        } catch (IOException exp) {
            exp.printStackTrace();
        }
    }
    /**
     * 写文件
     * @param filePath
     * @param content
     * @throws IOException
     */
    private static void writeFile(String filePath, String content) throws IOException {
        File file = new File(filePath);
        System.out.println(filePath);
        // 创建文件输出流
        try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
            // 写入文件内容
            writer.print(content);
        }
    }
    /**
     * 去除网址前缀
     * @param url
     * @return
     */
    private static String removeUrlPrefix(String url) {
        // 去除"http://"前缀
        if (url.startsWith("http://")) {
            url = url.substring(7);
        }
        // 去除"https://"前缀
        else if (url.startsWith("https://")) {
            url = url.substring(8);
        }
        return url;
    }
}
URLmoveListener
监听器:控制网页界面前进、后退
package com.xhf.keshe.listener;
import com.xhf.keshe.Main;
import com.xhf.keshe.utils.URLList;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class URLmoveListener implements ActionListener {
    public static int FORWARD = 0;
    public static int BACKEND = 1;
    private int direction;
    public URLmoveListener(int direction) {
        this.direction = direction;
    }
    public void actionPerformed(ActionEvent e) {
        boolean flag = false;
        // 向前移动
        if (direction == FORWARD) {
            flag = URLList.right();
            if (!flag) {
                JOptionPane.showMessageDialog(null, "已经是最新的网页了");
            }
        }else if (direction == BACKEND) {
            flag = URLList.left();
            if (!flag) {
                JOptionPane.showMessageDialog(null, "已经是最旧的网页了");
            }
        }
        if (flag) {
            Main.refreshWebSite(URLList.getCur());
        }
    }
}




















