Java小型操作系统模拟
- 项目说明
- 第一阶段:反射结合策略模式搭建基本的命令结构
- 第二阶段:注解结合反射与策略模式,将结构进一步规范
- 第三阶段:开启新的窗口,将控制台输入切换到新窗口中,同时创建右键菜单,使效果贴近命令行
- 第四阶段,发现IDEA的编译与命令行编译不一致且导致类装载出错的情况进行处理--重新编写初始化的装载方法
- 第五阶段,解决打包为jar后的装载问题及采用脚本自动编译
- 项目下载
项目说明
主要是为了学习Java反射的知识,以及对操作系统的一些概念进行回顾,搭建了一个小型的操作系统,包括基本的一些命令,如:clear、help、cd、mkdir、ls等;同时支持用户及角色创建,密码登录等,相关文件采用加密存储在本地;同时采用资源分配的形式设计了磁盘资源和内存资源,采用首次适应、循环首次适应、最佳适应、最坏适应等策略;采用Java的Socket通信设计了简单的socket通信模式
第一阶段:反射结合策略模式搭建基本的命令结构
采用配置文件的形式指定命令的名称、描述与对应类的全路径,采用反射的形式创建对象并装载到容器中;策略模式结合HashMap的形式,可以较为方便的获取到命令对象,并执行对应的策略方法。
策略模式的体现,采用Map进行命令策略的管理
package os.strategy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author bbyh
* @date 2023-07-27 21:19
* @description
*/
public abstract class BaseStrategy {
private static final Map<String, BaseStrategy> STRATEGY_FACTORY = new HashMap<>(4);
private static final Map<String, String> STRATEGY_DOC = new HashMap<>(4);
private final String name;
private final String doc;
public BaseStrategy(String name, String doc) {
this.name = name;
this.doc = doc;
}
protected final void register() {
STRATEGY_FACTORY.put(name, this);
STRATEGY_DOC.put(name, doc);
}
public abstract void execute();
public static BaseStrategy getStrategy(String name) {
if (!STRATEGY_FACTORY.containsKey(name)) {
throw new UnsupportedOperationException("该策略还不支持");
}
return STRATEGY_FACTORY.get(name);
}
public static Set<String> getCommandNameSet() {
return new HashSet<>(STRATEGY_FACTORY.keySet());
}
public static Map<String, String> getCommandDoc() {
return new HashMap<>(STRATEGY_DOC);
}
}
采用配置文件结合反射装载对象;分为只装载指定目录,或装载指定目录及其子目录,要求都是基础策略类的子类
package os.util;
import os.strategy.BaseStrategy;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Properties;
/**
* @author bbyh
* @date 2023/7/28 12:19
*/
public class OsApplication {
private static final String PACKAGE_PREFIX = "src/";
private static final String DEFAULT_STRATEGY_PACKAGE = "strategy.impl";
private static final String OS_COMMAND_PROPERTIES = "os-command-config.properties";
private static final HashSet<Class<?>> CLASS_SET = new HashSet<>(4);
private static void load() {
for (Class<?> instance : CLASS_SET) {
if (instance.getSuperclass() == BaseStrategy.class) {
try (InputStream inputStream = Files.newInputStream(Paths.get(OS_COMMAND_PROPERTIES))) {
Properties properties = new Properties();
properties.load(inputStream);
if (properties.containsKey(instance.getName())) {
String[] split = properties.getProperty(instance.getName()).split(",");
try {
Constructor<?> constructor = instance.getConstructor(String.class, String.class);
Object newInstance = constructor.newInstance(split[0], split[1]);
Method register = newInstance.getClass().getSuperclass().getDeclaredMethod("register");
register.setAccessible(true);
register.invoke(newInstance);
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
public static void runWithSpecifiedDirectory(Class<?> application) {
initSpecifiedDirectory(application.getPackage().getName() + "." + DEFAULT_STRATEGY_PACKAGE);
load();
}
private static void initSpecifiedDirectory(String scanPackage) {
File basePackage = new File(PACKAGE_PREFIX + scanPackage.replace(".", "/"));
File[] files = basePackage.listFiles(file -> file.getName().endsWith(".java"));
loadClasses(scanPackage, files);
}
private static void loadClasses(String scanPackage, File[] files) {
if (files != null) {
for (File file : files) {
try {
Class<?> instance = Class.forName(scanPackage + "." + file.getName().replace(".java", ""));
CLASS_SET.add(instance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
public static void runWithRecursiveDirectory(Class<?> application) {
initRecursiveDirectory(application.getPackage().getName());
load();
}
private static void initRecursiveDirectory(String scanPackage) {
File basePackage = new File(PACKAGE_PREFIX + scanPackage.replace(".", "/"));
File[] files = basePackage.listFiles(file -> {
if (file.isDirectory()) {
initRecursiveDirectory(scanPackage + "." + file.getName());
}
return file.getName().endsWith(".java");
});
loadClasses(scanPackage, files);
}
}
配置文件
os.strategy.impl.ClearStrategy=clear,清屏命令,清空屏幕
os.strategy.impl.HelpStrategy=help,帮助命令,查看当前系统支持的命令
项目结构
第二阶段:注解结合反射与策略模式,将结构进一步规范
采用类似SpringBoot的启动方式,自动装载指定目录下的命令类,采用注解的形式设置命令的名称与描述
采用注解设置启动装载包目录名,以及各个策略类的目录名称与相关说明
package os.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author bbyh
* @date 2023/7/2 15:29
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface StrategyScan {
String value() default "strategy.impl";
}
help命令,简单输出系统支持的命令和说明
package os.strategy.impl;
import os.annotation.StrategyAnnotation;
import os.annotation.StrategyDocAnnotation;
import os.strategy.BaseStrategy;
import java.util.Map;
import java.util.Set;
/**
* @author bbyh
* @date 2023-07-27 21:26
* @description
*/
@StrategyAnnotation(commandName = "help")
@StrategyDocAnnotation(commandDoc = "帮助命令,查看当前系统支持的命令")
public class HelpStrategy extends BaseStrategy {
public HelpStrategy(String name, String doc) {
super(name, doc);
}
@Override
public void execute() {
System.out.println("系统支持的命令");
Set<String> commandNameSet = BaseStrategy.getCommandNameSet();
Map<String, String> commandDoc = BaseStrategy.getCommandDoc();
for (String commandName : commandNameSet) {
System.out.println(commandName + ":" + commandDoc.get(commandName));
}
}
}
同样加载指定目录下携带注解的策略子类,或者加载目录及其子目录的所有符合条件的类
package os.util;
import os.annotation.StrategyAnnotation;
import os.annotation.StrategyDocAnnotation;
import os.annotation.StrategyScan;
import os.strategy.BaseStrategy;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
/**
* @author bbyh
* @date 2023/7/28 12:19
*/
public class OsApplication {
private static final String PACKAGE_PREFIX = "src/";
private static final HashSet<Class<?>> CLASS_SET = new HashSet<>(4);
private static void load() {
for (Class<?> instance : CLASS_SET) {
if (instance.getSuperclass() == BaseStrategy.class) {
try {
StrategyAnnotation nameAnnotation = instance.getAnnotation(StrategyAnnotation.class);
StrategyDocAnnotation docAnnotation = instance.getAnnotation(StrategyDocAnnotation.class);
if (nameAnnotation != null && docAnnotation != null) {
Constructor<?> constructor = instance.getConstructor(String.class, String.class);
Object newInstance = constructor.newInstance(nameAnnotation.commandName(), docAnnotation.commandDoc());
Method register = newInstance.getClass().getSuperclass().getDeclaredMethod("register");
register.setAccessible(true);
register.invoke(newInstance);
}
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 加载配置目录下的添加了StrategyAnnotation注解的类
*
* @param application 启动类
*/
public static void runWithSpecifiedDirectory(Class<?> application) {
StrategyScan strategyScan = application.getAnnotation(StrategyScan.class);
initSpecifiedDirectory(application.getPackage().getName() + "." + strategyScan.value());
load();
}
private static void initSpecifiedDirectory(String scanPackage) {
File basePackage = new File(PACKAGE_PREFIX + scanPackage.replace(".", "/"));
File[] files = basePackage.listFiles(file -> file.getName().endsWith(".java"));
loadClasses(scanPackage, files);
}
private static void loadClasses(String scanPackage, File[] files) {
if (files != null) {
for (File file : files) {
try {
Class<?> instance = Class.forName(scanPackage + "." + file.getName().replace(".java", ""));
CLASS_SET.add(instance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
/**
* 加载配置目录及其子目录下的添加了StrategyAnnotation注解的类
*
* @param application 启动类
*/
public static void runWithRecursiveDirectory(Class<?> application) {
initRecursiveDirectory(application.getPackage().getName());
load();
}
private static void initRecursiveDirectory(String scanPackage) {
File basePackage = new File(PACKAGE_PREFIX + scanPackage.replace(".", "/"));
File[] files = basePackage.listFiles(file -> {
if (file.isDirectory()) {
initRecursiveDirectory(scanPackage + "." + file.getName());
}
return file.getName().endsWith(".java");
});
loadClasses(scanPackage, files);
}
}
项目结构
第三阶段:开启新的窗口,将控制台输入切换到新窗口中,同时创建右键菜单,使效果贴近命令行
经过测试发现,上面两个系统在初始化加载类时有一些小问题,因为在实际使用时都是执行class文件,而在IDEA里面的执行是以Java文件为路径的,需要进行一些简单的小修改,当前这个阶段主要以IDEA里面的运行为主,后面再对路径和初始化逻辑进行一些调整,来保证兼容性
主窗体
package os.util;
import os.strategy.BaseStrategy;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.io.IOException;
/**
* @author bbyh
* @date 2023-07-29 15:51
* @description
*/
public class MainFrame extends JFrame {
public static final JLabel DISPLAY = new JLabel();
private static final String DEFAULT_WORD_DIR = "C:\\root>";
public MainFrame() {
setTitle("BBYH OS System");
setSize(1000, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
JScrollPane scrollPane = new JScrollPane(DISPLAY);
DISPLAY.setOpaque(true);
DISPLAY.setBackground(Color.WHITE);
setLayout(new GridLayout(1, 1));
add(scrollPane);
setVisible(true);
Font font = new Font("Consolos", Font.PLAIN, 24);
DISPLAY.setFont(font);
DISPLAY.setVerticalAlignment(SwingConstants.TOP);
DISPLAY.setFocusable(true);
init();
// 右键菜单
JPopupMenu rightMenu = new JPopupMenu();
JMenuItem backgroundItem = new JMenuItem();
JMenuItem fontItem = new JMenuItem();
JMenuItem copyItem = new JMenuItem();
backgroundItem.setFont(font);
fontItem.setFont(font);
copyItem.setFont(font);
backgroundItem.setText("设置背景颜色");
fontItem.setText("设置字体颜色");
copyItem.setText("复制剪切版内容");
rightMenu.add(backgroundItem);
rightMenu.add(fontItem);
rightMenu.add(copyItem);
backgroundItem.addActionListener(evt -> {
Color color = JColorChooser.showDialog(null, "设置背景颜色", Color.WHITE);
if (color != null) {
DISPLAY.setBackground(color);
}
});
fontItem.addActionListener(evt -> {
Color color = JColorChooser.showDialog(null, "设置字体颜色", Color.BLACK);
if (color != null) {
DISPLAY.setForeground(color);
}
});
copyItem.addActionListener(evt -> {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
Transferable transferable = clipboard.getContents(null);
if (transferable != null) {
if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
String data = (String) clipboard.getData(DataFlavor.stringFlavor);
appendDisplay(data);
} catch (UnsupportedFlavorException | IOException e) {
throw new RuntimeException(e);
}
}
}
});
DISPLAY.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
char keyChar = e.getKeyChar();
if (keyChar == KeyEvent.VK_BACK_SPACE && !"".equals(getContent())) {
DISPLAY.setText(getContent().substring(0, getContent().length() - 1));
appendDisplay("");
} else if (keyChar == KeyEvent.VK_ENTER) {
appendDisplay("<br />");
try {
BaseStrategy.getStrategy(Context.getNextCommand()).execute();
} catch (Exception ex) {
appendDisplay(ex.getMessage() + "<br />");
}
appendDisplay(Context.getWorkDir());
} else if (Character.isLetterOrDigit(keyChar)) {
appendDisplay(String.valueOf(keyChar));
}
}
});
DISPLAY.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() == MouseEvent.BUTTON3) {
rightMenu.show(DISPLAY, e.getX(), e.getY() - 10);
}
}
});
}
private static void init() {
Context.setWorkDir(DEFAULT_WORD_DIR);
appendDisplay(DEFAULT_WORD_DIR);
}
public static void appendDisplay(String content) {
DISPLAY.setText("<html><body style='padding: 20px 10px'>" + getContent() + content + "</body></html>");
}
private static String getContent() {
return DISPLAY.getText().replace("<html><body style='padding: 20px 10px'>", "").replace("</body></html>", "");
}
}
Context上下文操作类
package os.util;
/**
* @author bbyh
* @date 2023-07-27 22:23
* @description
*/
public class Context {
private static String workDir;
public static String getWorkDir() {
return workDir;
}
public static void setWorkDir(String workDir) {
Context.workDir = workDir;
}
public static String getNextCommand() {
String text = MainFrame.DISPLAY.getText();
return text.substring(text.lastIndexOf(getWorkDir()) + getWorkDir().length(), text.lastIndexOf("<br /></body></html>"));
}
}
主程序
package os;
import os.annotation.StrategyScan;
import os.util.MainFrame;
import os.util.OsApplication;
/**
* @author bbyh
* @date 2023-07-27 21:18
* @description
*/
@StrategyScan
public class Main {
public static void main(String[] args) {
OsApplication.runWithRecursiveDirectory(Main.class);
new MainFrame();
}
}
测试运行效果展示
第四阶段,发现IDEA的编译与命令行编译不一致且导致类装载出错的情况进行处理–重新编写初始化的装载方法
采用getResource方法获取类路径,来装在class文件,同时采用脚本来解决javac命令行执行时无法编译不被别的文件依赖的策略实现类
重新编写的装载方法,采用递归装载,装载启动类目录下及其子目录中带有注解的策略实现类
package os.util;
import os.annotation.StrategyAnnotation;
import os.annotation.StrategyDocAnnotation;
import os.strategy.BaseStrategy;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Objects;
/**
* @author bbyh
* @date 2023/7/28 12:19
*/
public class OsApplication {
private static final HashSet<Class<?>> CLASS_SET = new HashSet<>(4);
private static String BASE_PATH;
/**
* 加载配置目录及其子目录下的添加了StrategyAnnotation注解的类
*
* @param application 启动类
*/
public static void runWithRecursiveDirectory(Class<?> application) {
BASE_PATH = Objects.requireNonNull(application.getResource("/")).getPath();
initRecursiveDirectory(application.getPackage().getName());
load();
}
private static void initRecursiveDirectory(String packageName) {
File[] files = new File(BASE_PATH + packageName.replace(".", System.getProperty("file.separator"))).listFiles(file -> {
if (file.isDirectory()) {
initRecursiveDirectory(packageName + "." + file.getName());
}
return file.getName().endsWith(".class");
});
loadClasses(packageName, files);
}
private static void load() {
for (Class<?> instance : CLASS_SET) {
if (instance.getSuperclass() == BaseStrategy.class) {
try {
StrategyAnnotation nameAnnotation = instance.getAnnotation(StrategyAnnotation.class);
StrategyDocAnnotation docAnnotation = instance.getAnnotation(StrategyDocAnnotation.class);
if (nameAnnotation != null && docAnnotation != null) {
Constructor<?> constructor = instance.getConstructor(String.class, String.class);
Object newInstance = constructor.newInstance(nameAnnotation.commandName(), docAnnotation.commandDoc());
Method register = newInstance.getClass().getSuperclass().getDeclaredMethod("register");
register.setAccessible(true);
register.invoke(newInstance);
}
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
}
private static void loadClasses(String packageName, File[] files) {
if (files != null) {
for (File file : files) {
try {
Class<?> instance = Class.forName(packageName + "." + file.getName().replace(".class", ""));
CLASS_SET.add(instance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
}
需要注意的一点是,目前采用getResource方法对于打包为jar包时则会报空指针错误,后续将考虑采用新方法装载来解决该问题
第五阶段,解决打包为jar后的装载问题及采用脚本自动编译
自动编译的脚本,Windows
mkdir out
cd src
javac -d ../out os/Main.java os/strategy/impl/*.java -encoding UTF-8
cd ../out
java os.Main
自动编译的脚本,Linux
#!/bin/bash
mkdir out
cd src
javac -d ../out os/Main.java os/strategy/impl/*.java -encoding UTF-8
cd ../out
java os.Main
需要注意几点小问题,脚本.sh需要在Linux下创建使用,同时需要给它赋予x执行权限(chmod +x javac_os_java.sh);然后在执行时还需要对XShell安装一下图形服务x11,参考文章:xshell-linux 启动 jmeter 报 No X11 DISPLAY variable was set, but this program performed …;不过这个Xming对中文的显示有问题,有点难办
主函数(根据启动不一样采用不一样的装载策略)
package os;
import os.annotation.StrategyScan;
import os.util.MainFrame;
import os.util.OsApplication;
import java.util.Objects;
/**
* @author bbyh
* @date 2023-07-27 21:18
* @description
*/
@StrategyScan
public class Main {
public static boolean applicationRunning = true;
private static final String FILE_PROTOCOL = "file";
private static final String JAR_PROTOCOL = "jar";
public static void main(String[] args) {
String protocol = Objects.requireNonNull(OsApplication.class.getResource("")).getProtocol();
if (Objects.equals(protocol, FILE_PROTOCOL)) {
OsApplication.runWithRecursiveDirectory(Main.class);
} else if (Objects.equals(protocol, JAR_PROTOCOL)) {
OsApplication.runWithRecursiveJar(Main.class);
}
new MainFrame();
}
}
装载工具类
package os.util;
import os.annotation.StrategyAnnotation;
import os.annotation.StrategyDocAnnotation;
import os.strategy.BaseStrategy;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Objects;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @author bbyh
* @date 2023/7/28 12:19
*/
public class OsApplication {
private static final HashSet<Class<?>> CLASS_SET = new HashSet<>(4);
private static String BASE_PATH;
public static final String FILE_SEPARATOR = "/";
private static final String PREFIX_FILE = "file:";
private static final String PREFIX_JAR = "jar:file:";
private static final String SUFFIX_CLASS = ".class";
/**
* 加载配置目录及其子目录下的添加了StrategyAnnotation注解的类
*
* @param application 启动类
*/
public static void runWithRecursiveDirectory(Class<?> application) {
BASE_PATH = Objects.requireNonNull(application.getResource("")).toString().replace(PREFIX_FILE, "");
String packageName = application.getPackage().getName();
BASE_PATH = BASE_PATH.substring(0, BASE_PATH.lastIndexOf("/" + packageName + "/")) + FILE_SEPARATOR;
initRecursiveDirectory(packageName);
load();
}
private static void initRecursiveDirectory(String packageName) {
File[] files = new File(BASE_PATH + packageName.replace(".", FILE_SEPARATOR)).listFiles(file -> {
if (file.isDirectory()) {
initRecursiveDirectory(packageName + "." + file.getName());
}
return file.getName().endsWith(SUFFIX_CLASS);
});
loadFileClasses(packageName, files);
}
private static void loadFileClasses(String packageName, File[] files) {
if (files != null) {
for (File file : files) {
try {
Class<?> instance = Class.forName(packageName + "." + file.getName().replace(SUFFIX_CLASS, ""));
CLASS_SET.add(instance);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}
private static void load() {
for (Class<?> instance : CLASS_SET) {
if (instance.getSuperclass() == BaseStrategy.class) {
try {
StrategyAnnotation nameAnnotation = instance.getAnnotation(StrategyAnnotation.class);
StrategyDocAnnotation docAnnotation = instance.getAnnotation(StrategyDocAnnotation.class);
if (nameAnnotation != null && docAnnotation != null) {
Constructor<?> constructor = instance.getConstructor(String.class, String.class);
Object newInstance = constructor.newInstance(nameAnnotation.commandName().split(" ")[0], docAnnotation.commandDoc());
Method register = newInstance.getClass().getSuperclass().getDeclaredMethod("register");
register.setAccessible(true);
register.invoke(newInstance);
}
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
}
}
}
public static void runWithRecursiveJar(Class<?> application) {
BASE_PATH = Objects.requireNonNull(application.getResource("")).toString().replace(PREFIX_JAR, "");
BASE_PATH = BASE_PATH.substring(0, BASE_PATH.lastIndexOf("!/" + application.getPackage().getName() + "/")) + FILE_SEPARATOR;
initRecursiveJar();
load();
}
private static void initRecursiveJar() {
try (JarFile jarFile = new JarFile(BASE_PATH)) {
Enumeration<JarEntry> jarEntryEnumeration = jarFile.entries();
while (jarEntryEnumeration.hasMoreElements()) {
JarEntry jarEntry = jarEntryEnumeration.nextElement();
if (jarEntry.getName().endsWith(SUFFIX_CLASS)) {
Class<?> instance = Class.forName(jarEntry.getName().replace(SUFFIX_CLASS, "").replace("/", "."));
CLASS_SET.add(instance);
}
}
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
运行效果
项目下载
Gitee链接–Java模拟操作系统