0

0

Java怎么实现HttpServer模拟前端接口调用

PHPz

PHPz

发布时间:2023-04-17 10:28:39

|

895人浏览过

|

来源于亿速云

转载

    执行结果展示

    这里是两个简单的测试展示,最后有更多的测试示例。

    访问根目录,然后会返回一句话(字符串)(注意:那条绿色的龙,只有使用浏览器访问才能看到的,图片本身也是属于一个请求的。) Content-Type: application/json

    Java怎么实现HttpServer模拟前端接口调用

    单独访问这张图片,返回值是图片(二进制数据)

    Java怎么实现HttpServer模拟前端接口调用

    立即学习Java免费学习笔记(深入)”;

    请求和响应配置文件

    所以只要用户提前设置好请求信息和响应信息,在访问特定请求时,就能返回特定数据。所以,我设计了一个简单的 xml 文件用来配置这些信息,使用 xml 配置比较方便,properties 配置文件无法表达层次信息,只能适用于简单的配置要求。

    一个大的 request_and_responses 代表许多个请求和响应配置,每一个 request_and_response 节点代表一个request 请求和 response 响应信息,里面包含了请求和响应的基本信息。GET 方式请求主要包括:(method) 请求方法 和 (path) 请求路径和参数。 POST 方法请求还包括一个 (param )请求参数。 response 包括:content_type(响应内容类型) 和 value(响应内容)。

    GET 和 POST 方式的区别在于,GET 方式的请求路径和请求参数是在一起的(都在请求头中,没有请求体),而 POST 方式的请求参数则是在请求体里面的,请求头和请求体之间有一个 CRLF 分隔。

    xml 文件
    
    
    	
    		
    	
    		
    			GET
    			/
    		
    		
    			application/json
    			I love you yesterday and today!
    		
    	
    	
    	
    		
    			GET
    			/login?account=123&pwd=456
    		
    		
    		
    			application/json
    			success
    		
    	
    	
    	
    		
    			GET
    			/query/龙
    		
    		
    			application/json
    			龙是中国等东亚国家古代神话传说生活于海中的神异生物。
    		
    	
    	
    	
    		
    			POST
    			/login
    			account=123&pwd=456
    		
    		
    		
    			application/json
    			{"result":success}
    		
    	
    	
    	
    	
    		
    			POST
    			/login
    			workId=12345
    		
    		
    		
    			application/json
    			{"result":"success", "data":{"name":"李工", "sex":"男", "age":35}}
    		 
    	
    	
    	
    	
    		
    			GET
    			/pictures/husky.jpeg
    		
    		
    		
    			image/jpeg
    			D:/DB/husky.jpeg
    		 
    	
    	
    	
    	
    		
    			GET
    			/favicon.ico
    		
    		
    		
    			image/webp
    			D:/DB/favicon.ico
    		 
    	
    	
    
    xml 映射的实体类

    xml 中的信息,读取到内存中,使用一个实体类来对信息进行封装。

    package com.dragon;
    
    public class RequestAndResponse {
    	private String method;
    	private String path;
    	private String param;
    	private String content_type;
    	private String value;
    	
    	public String getMethod() {
    		return method;
    	}
    	public void setMethod(String method) {
    		this.method = method;
    	}
    	public String getPath() {
    		return path;
    	}
    	public void setPath(String path) {
    		this.path = path;
    	}
    	public String getParam() {
    		return param;
    	}
    	public void setParam(String param) {
    		this.param = param;
    	}
    	public String getContent_type() {
    		return content_type;
    	}
    	public void setContent_type(String content_type) {
    		this.content_type = content_type;
    	}
    	public String getValue() {
    		return value;
    	}
    	public void setValue(String value) {
    		this.value = value;
    	}
    	
    	@Override
    	public String toString() {
    		return "RequestAndResponse [method=" + method + ", path=" + path + ", param=" + param + ", content_type="
    				+ content_type + ", value=" + value + "]";
    	}
    }
    xml 文件解析器类

    使用一个类专门解析 xml 文件为Java 对象,然后使用一个 List 集合来存储所有的对象。

    注:不太会取名字,有点太长了,凑合着看吧!哈哈。 注:这里使用一个xml解析的jar包:dom4j。

    package com.dragon;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import org.dom4j.Document;
    import org.dom4j.DocumentException;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    /**
     * 解析 xml 文件中配置的用户请求和响应数据。
     * */
    public class RequestAndResponseResolver {
    	private static final String method = "method";
    	private static final String path = "path";
    	private static final String param = "param";
    	private static final String content_type = "content_type";
    	private static final String value = "value";
    	
    	public static List listRequestAndResponse(String filePath) throws DocumentException{
    		File file = new File(filePath); 
    		SAXReader reader = new SAXReader();
    		Document doc = reader.read(file);
    		
    		Element root = doc.getRootElement();
    		//获取根元素下面所有的子元素,利用迭代器方式
    		Iterator it = root.elementIterator();
    		
    		List requestAndResponses = new ArrayList<>();
    		while (it.hasNext()) {
    			//取出元素request_and_response
    			Element e = (Element)it.next();
    			//依次遍历每一个 request_and_response,获取相应的信息
    			Element request = e.element("request");
    			Element response = e.element("response");
    			
    			RequestAndResponse requestAndResponse = new RequestAndResponse();
    			requestAndResponse.setMethod(request.elementText(method));
    			requestAndResponse.setPath(request.elementText(path));
    			requestAndResponse.setParam(request.elementText(param));   //GET 方式,这个属性为 null
    			requestAndResponse.setContent_type(response.elementText(content_type));
    			requestAndResponse.setValue(response.elementText(value));
    			
    			requestAndResponses.add(requestAndResponse);
    		}
    		
    		return requestAndResponses;
    	}
    }

    接收请求并处理的部分

    下面介绍一下,使用 Socket 接收并处理请求的部分。 这里涉及的知识和使用 Socket 基本上都是一样的,唯一的区别就是对于内容本身的处理,因为内容本身是包含数据和非数据部分的。(站在 HTTP 的角度,只能看到数据部分。) 使用 Socket 编程,简单来说就是监听一个端口,一旦有连接到来,就进行处理。(这里使用传统的 BIO,NIO 那部分我不会。)

    这里我的处理是,使用一个线程池进行处理,每一个连接使用一个线程进行处理。关于这个类(Server 类)的完整代码,见下面。

    	public void receive() {
    		//使用线程池处理请求
    		ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUMBER);
    		
    		while (true) {
    			try {
    				Socket connection = server.accept();
    				pool.submit(new UserConnection(connection));
    			} catch (IOException e) {
    				System.out.println(this.getDate()+" 用户连接断开");
    				e.printStackTrace();
    			}
    		}
    	}
    接收请求的代码:Server 类
    package com.dragon;
    
    import java.io.IOException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Server {
    	private static final int THREAD_NUMBER = 10;
    	private ServerSocket server;
    	private int port;
    	
    	public Server(int port) {
    		this.port = port;
    	}
    	
    	//启动服务。
    	public void start() {
    		try {
    			server = new ServerSocket(port);
    			System.out.println(this.getDate()+" 服务启动!");
    			this.receive();
    		} catch (IOException e) {
    			System.out.println(this.getDate()+" 服务启动失败!");
    			e.printStackTrace();
    		}
    	}
    	
    	public void receive() {
    		//使用线程池处理请求
    		ExecutorService pool = Executors.newFixedThreadPool(THREAD_NUMBER);
    		
    		while (true) {
    			try {
    				Socket connection = server.accept();
    				pool.submit(new UserConnection(connection));
    			} catch (IOException e) {
    				System.out.println(this.getDate()+" 用户连接断开");
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	
    	
    	private String getDate() {
    		String format = "yyyy-MM-dd HH:mm:ss";
    		Date now = new Date();
    		SimpleDateFormat dateFormat = new SimpleDateFormat(format);
    		return dateFormat.format(now);
    	}
    }

    Http 请求报文从 TCP 的层次来看就是一段二进制数据流(网络是分层的),所以我们可以直接使用 TCP 接收这个流, 因为涉及二进制数据(如文件上传)的报文解析比较复杂,我也不知道该怎么做,所以这里我只是测试简单的不含有二进制文件的请求。)

    注:因为具体的解析也是很复杂的,这涉及到 HTTP 报文的结构,但是如果不涉及文件上传,那么整个报文都是一些字符数据,所以一次性读取全部请求报文,然后转成字符串,使用字符串来进行解析。

    in = connection.getInputStream();
    out = connection.getOutputStream();
    			
    //这个数字是随便设置的,因为要一次性读取整个请求报文,不能太小。(但是其实这个也很大了)
    byte[] b = new byte[5*1024];
    BufferedInputStream input = new BufferedInputStream(in);
    int count = input.read(b);
    String requestMessage = new String(b, 0, count);
    System.out.println("====================报文分隔符上界===================");
    System.out.println(requestMessage);
    System.out.println("====================报文分隔符下界===================");
    处理请求代码:UserConnection 类

    请求和响应信息初始化 说明:使用静态初始化块来初始化信息,将用户提前配置好的 xml 信息读取入内存,前面提到过这部分。

    // 初始化配置好的信息
    	static {
    		try {
    			requestAndResponses = RequestAndResponseResolver.listRequestAndResponse("./src/com/dragon/request_and_response.xml");
    		} catch (DocumentException e) {
    			e.printStackTrace();
    		}
    	}

    请求处理和获取响应信息 因为这里是模拟调用,所以我主要关注请求头中的三个部分数据:请求方法(method)、请求路径(path)、请求参数(param)。 对于 GET 方式和 POST 方式分开进行处理,上面简单介绍了 GET 和 POST 的区别(但是不够详细,可以去参考网上的其它资料了解)。

    通过这段代码,如果是 GET 方式,就将 RequestAndResponse 对象中的 content_type(返回值数据类型) 和 value (返回值数据)取出来,并赋值给局部变量 content_type 和 value。

    if ("GET".compareTo(method) == 0) {
    	for (RequestAndResponse r : requestAndResponses) {
    		//这里需要对 get 方式时的请求进行解码,因为一些非 ASCII 码字符会被编码,比如汉字。
    		path = URLDecoder.decode(path, ENCODE);
    		if (r.getMethod().equals(method) && r.getPath().equals(path)) {
    			content_type = r.getContent_type();
    			value = r.getValue();
    			break;
    		}
    	}
    } else {
    	//POST 方式,请求参数是在请求体中的,请求头和请求体中间有一个换行符。
    	String param = requestMessage.substring(requestMessage.lastIndexOf(CRLF) + 2); //这里是不包括 CRLF 的两个字符的。
    	for (RequestAndResponse r : requestAndResponses) {                 //因为这个get方式的 参数为空,所以这里必须是 param 在前。
    		if (r.getMethod().equals(method) && r.getPath().equals(path) && param.equals(r.getParam())) {
    			content_type = r.getContent_type();
    			value = r.getValue();
    			break;
    		}
    	}
    }

    这里介绍一个知识:URL 中的字符是特定的,不允许中文等字符的出现,所以发送请求时会对中文等字符进行编码,如果直接使用 equals 方法的,当然不会相等了,所以需要先对数据进行解码,然后再调用 equals 方法进行处理。这个是我们平时广泛使用 的东西,有时候使用浏览器可以看到带很多奇怪字符 URL,它们都是被处理过的。

    举一个简单的例子:

    String str = "我爱你";
    String en_str = java.net.URLEncoder.encode(str, "UTF-8");
    String de_str = java.net.URLDecoder.decode(en_str, "UTF-8");
    System.out.println("编码字符:" + en_str);
    System.out.println("解码字符:" + de_str);

    Java怎么实现HttpServer模拟前端接口调用

    注意:这里有一个特殊的情况,如果发起了没有配置的请求方法和路径,那么程序会出错。所以,这里的 content_type 和 value 有一个默认的值,而且非常有趣!

    BlackBox AI
    BlackBox AI

    AI编程助手,智能对话问答助手

    下载

    Java怎么实现HttpServer模拟前端接口调用

    执行响应 响应信息主要关注几点:响应信息长度(Content-Length)(按字节数计算)、响应内容类型(Content-Type)。

    虽然发送的请求里不能带二进制文件,但是响应信息是可以返回文件的,而且使用 Content-Length (一次性发送),不使用 Chunked 分块发送(这里我还不太明白,而且只是模拟,应该使用一些简单的小文件。)。

    下面是区分响应类型为 json (字符串) 还是 文件(二进制数据) 的代码:

    如果是字符串,则 value 的值是字符串的值,如果是文件,则 value 的值为一个具体的本地路径。(不应该使用网络图片,即使修改程序可以做到也没有必要,因为这样就需要依赖网络了。)

    	//这里我只处理字符串类和文件类两种响应体
    	//响应体
    	int len = 0;
    	String responseBody = null;   //响应值是 json 数据
    	File file = null; //响应值是 文件
    	if (content_type.equals("application/json")) {  //如果是 json 数据,否则就是 文件类数据(图片、文档或其它文件)
    		 responseBody = value;
    		 len = responseBody.getBytes().length;   //响应体的字节数,注意是字节数!
    	} else {
    		 file = new File(value);
    		 len = (int) file.length();
    	}

    然后就可以准备发送响应数据了,下面是发送响应的代码,注意报文的具体结构。

    	//响应头
    	responseHeader.append("HTTP/1.1").append(BLANK);
    	responseHeader.append(200).append(BLANK);
    	responseHeader.append("OK").append(CRLF);
    	responseHeader.append("Server:"+"CrazyDragon").append(CRLF);
    	responseHeader.append("Date:").append(BLANK).append(date).append(CRLF);
    	responseHeader.append("Content-Type:").append(BLANK).append(content_type).append(CRLF);
    	responseHeader.append("Content-Length:").append(BLANK).append(len).append(CRLF);
    	responseHeader.append(CRLF);
    	
    	//如果 字符串变量 responseBody 不为空,则说明返回值是 json 数据(字符串)
    	//否则就是文件类的流了。
    	if (responseBody != null) {
    		String response = responseHeader.toString() + responseBody;
    		out.write(response.getBytes("UTF-8"));    
    	} else {
    		out.write(responseHeader.toString().getBytes("UTF-8"));  
    		
    		int hasRead = 0;
    		byte[] data = new byte[4*1024];
    		try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
    			while ((hasRead = inputStream.read(data)) != -1) {
    				out.write(data, 0, hasRead);
    			}
    		}
    	}
    	out.flush();   //必要的刷新流操作。

    User Connection 的完整代码:

    package com.dragon;
    
    import java.io.BufferedInputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.net.URLDecoder;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.List;
    import java.util.Locale;
    import java.util.TimeZone;
    
    import org.dom4j.DocumentException;
    
    public class UserConnection implements Runnable{
    	private static final String BLANK = " ";
    	private static final String CRLF = "\r\n"; //换行符,不能写反了!
    	private static final String ENCODE = "UTF-8";
    	private static final String default_content_type = "application/json";   //当任何匹配路径都没有时。
    	private static final String default_value = "404 NOT FOUND!\n没有找到你配置的请求!";
    	
    	
    	private static List requestAndResponses;
    	private Socket connection;
    	
    	
    	// 初始化配置好的信息
    	static {
    		try {
    			requestAndResponses = RequestAndResponseResolver.listRequestAndResponse("./src/com/dragon/request_and_response.xml");
    		} catch (DocumentException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	
    	
    	public UserConnection(Socket connection) {
    		this.connection = connection;
    	}
    	
    	@Override
    	public void run() {
    		InputStream in = null;
    		OutputStream out = null;
    		try {
    			in = connection.getInputStream();
    			out = connection.getOutputStream();
    			
    			//这个数字是随便设置的,因为要一次性读取整个请求报文,不能太小。(但是其实这个也很大了)
    			byte[] b = new byte[5*1024];
    			BufferedInputStream input = new BufferedInputStream(in);
    			int count = input.read(b);
    			String requestMessage = new String(b, 0, count);
    			System.out.println("====================报文分隔符上界===================");
    			System.out.println(requestMessage);
    			System.out.println("====================报文分隔符下界===================");
    
    			//以第一个 换行符 CRLF 为界限取出 请求路径和请求参数
    			String requestLine = requestMessage.substring(0, requestMessage.indexOf(CRLF));
    			String[] line = requestLine.split("\\s");
    			String method = line[0];  //考虑大小写。
    			String path = line[1];  
    			//这个数组是有三个元素,最后一个是 协议的版本,这里不需要,就不处理了。
    			String content_type = default_content_type;
    			String value = default_value;
    			if ("GET".compareTo(method) == 0) {
    			//	System.out.println("请求方式:" + method + " 请求路径(含参数):" + path);
    				for (RequestAndResponse r : requestAndResponses) {
    					//这里需要对 get 方式时的请求进行解码,因为一些非 ASCII 码字符会被编码,比如汉字。
    					path = URLDecoder.decode(path, ENCODE);
    					if (r.getMethod().equals(method) && r.getPath().equals(path)) {
    						content_type = r.getContent_type();
    						value = r.getValue();
    						break;
    					}
    				}
    			} else {
    				//POST 方式,请求参数是在请求体中的,请求头和请求体中间有一个换行符。
    				String param = requestMessage.substring(requestMessage.lastIndexOf(CRLF) + 2); //这里是不包括 CRLF 的两个字符的。
    				for (RequestAndResponse r : requestAndResponses) {                 //因为这个get方式的 参数为空,所以这里必须是 param 在前。
    					if (r.getMethod().equals(method) && r.getPath().equals(path) && param.equals(r.getParam())) {
    						content_type = r.getContent_type();
    						value = r.getValue();
    						System.out.println(content_type+" "+value);
    						break;
    					}
    				}
    			}
    		
    			StringBuilder responseHeader = new StringBuilder();
    			String date = this.getDate();
    			
    			
    			//这里我只处理字符串类和文件类两种响应体
    			//响应体
    			int len = 0;
    			String responseBody = null;   //响应值是 json 数据
    			File file = null; //响应值是 文件
    			if (content_type.equals("application/json")) {  //如果是 json 数据,否则就是 文件类数据(图片、文档或其它文件)
    				 responseBody = value;
    				 len = responseBody.getBytes().length;   //响应体的字节数,注意是字节数!
    			} else {
    				 file = new File(value);
    				 len = (int) file.length();
    			}
    			
    			//响应头
    			responseHeader.append("HTTP/1.1").append(BLANK);
    			responseHeader.append(200).append(BLANK);
    			responseHeader.append("OK").append(CRLF);
    			responseHeader.append("Server:"+"CrazyDragon").append(CRLF);
    			responseHeader.append("Date:").append(BLANK).append(date).append(CRLF);
    			responseHeader.append("Content-Type:").append(BLANK).append(content_type).append(CRLF);
    			responseHeader.append("Content-Length:").append(BLANK).append(len).append(CRLF);
    			responseHeader.append(CRLF);
    			
    			//如果 字符串变量 responseBody 不为空,则说明返回值是 json 数据(字符串)
    			//否则就是文件类的流了。
    			if (responseBody != null) {
    				String response = responseHeader.toString() + responseBody;
    				out.write(response.getBytes("UTF-8"));    
    			} else {
    				out.write(responseHeader.toString().getBytes("UTF-8"));  
    				
    				int hasRead = 0;
    				byte[] data = new byte[4*1024];
    				try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
    					while ((hasRead = inputStream.read(data)) != -1) {
    						out.write(data, 0, hasRead);
    					}
    				}
    			}
    			out.flush();   //必要的刷新流操作。
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			try {
    				if (in != null)
    					in.close();
    			} catch (IOException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    	
    	private String getDate() {
    		Date date = new Date();
    		SimpleDateFormat format = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss 'GMT'", Locale.CHINA);
    		format.setTimeZone(TimeZone.getTimeZone("GMT")); // 设置时区为GMT  
    		return format.format(date);
    	}
    }

    主程序类:Main

    package com.dragon;
    
    public class Main {
    	public static void main(String[] args) {
    		Server server = new Server(9000);
    		server.start();		
    	}
    }

    更多的测试示例

    请求方式:GET 请求路径和参数:/query/龙 预期的响应类型:application/json 预期的响应值:龙是中国等东亚国家古代神话传说生活于海中的神异生物。 测试结果:

    Java怎么实现HttpServer模拟前端接口调用

    请求方式:GET 请求路径和参数:/login?account=123&pwd=456 预期的响应类型:application/json 预期的响应值:success 测试结果:

    Java怎么实现HttpServer模拟前端接口调用

    请求方式:GET 请求路径和参数:/pictures/husky.jpeg 预期的响应类型:image/jpeg 预期的响应值:一张图片(地址为:D:/DB/husky.jpeg)

    请求方式:POST 请求路径:/login 请求参数:account=123&pwd=456 预期的响应类型:application/json 预期的响应值:{“result”:success} 测试结果:

    Java怎么实现HttpServer模拟前端接口调用

    注:这是使用 HttpClient 发送的 POST 请求。

    Java怎么实现HttpServer模拟前端接口调用

    接收到的 POST 请求:

    Java怎么实现HttpServer模拟前端接口调用

    接收到的 GET 请求(含中文参数): /query/龙 注意:“龙” 已经被编码了。

    Java怎么实现HttpServer模拟前端接口调用

    相关文章

    java速学教程(入门到精通)
    java速学教程(入门到精通)

    java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

    下载

    相关标签:

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

    相关专题

    更多
    Golang gRPC 服务开发与Protobuf实战
    Golang gRPC 服务开发与Protobuf实战

    本专题系统讲解 Golang 在 gRPC 服务开发中的完整实践,涵盖 Protobuf 定义与代码生成、gRPC 服务端与客户端实现、流式 RPC(Unary/Server/Client/Bidirectional)、错误处理、拦截器、中间件以及与 HTTP/REST 的对接方案。通过实际案例,帮助学习者掌握 使用 Go 构建高性能、强类型、可扩展的 RPC 服务体系,适用于微服务与内部系统通信场景。

    8

    2026.01.15

    公务员递补名单公布时间 公务员递补要求
    公务员递补名单公布时间 公务员递补要求

    公务员递补名单公布时间不固定,通常在面试前,由招录单位(如国家知识产权局、海关等)发布,依据是原入围考生放弃资格,会按笔试成绩从高到低递补,递补考生需按公告要求限时确认并提交材料,及时参加面试/体检等后续环节。要求核心是按招录单位公告及时响应、提交材料(确认书、资格复审材料)并准时参加面试。

    44

    2026.01.15

    公务员调剂条件 2026调剂公告时间
    公务员调剂条件 2026调剂公告时间

    (一)符合拟调剂职位所要求的资格条件。 (二)公共科目笔试成绩同时达到拟调剂职位和原报考职位的合格分数线,且考试类别相同。 拟调剂职位设置了专业科目笔试条件的,专业科目笔试成绩还须同时达到合格分数线,且考试类别相同。 (三)未进入原报考职位面试人员名单。

    58

    2026.01.15

    国考成绩查询入口 国考分数公布时间2026
    国考成绩查询入口 国考分数公布时间2026

    笔试成绩查询入口已开通,考生可登录国家公务员局中央机关及其直属机构2026年度考试录用公务员专题网站http://bm.scs.gov.cn/pp/gkweb/core/web/ui/business/examResult/written_result.html,查询笔试成绩和合格分数线,点击“笔试成绩查询”按钮,凭借身份证及准考证进行查询。

    11

    2026.01.15

    Java 桌面应用开发(JavaFX 实战)
    Java 桌面应用开发(JavaFX 实战)

    本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

    65

    2026.01.14

    php与html混编教程大全
    php与html混编教程大全

    本专题整合了php和html混编相关教程,阅读专题下面的文章了解更多详细内容。

    36

    2026.01.13

    PHP 高性能
    PHP 高性能

    本专题整合了PHP高性能相关教程大全,阅读专题下面的文章了解更多详细内容。

    75

    2026.01.13

    MySQL数据库报错常见问题及解决方法大全
    MySQL数据库报错常见问题及解决方法大全

    本专题整合了MySQL数据库报错常见问题及解决方法,阅读专题下面的文章了解更多详细内容。

    21

    2026.01.13

    PHP 文件上传
    PHP 文件上传

    本专题整合了PHP实现文件上传相关教程,阅读专题下面的文章了解更多详细内容。

    35

    2026.01.13

    热门下载

    更多
    网站特效
    /
    网站源码
    /
    网站素材
    /
    前端模板

    精品课程

    更多
    相关推荐
    /
    热门推荐
    /
    最新课程
    Kotlin 教程
    Kotlin 教程

    共23课时 | 2.5万人学习

    C# 教程
    C# 教程

    共94课时 | 6.8万人学习

    Java 教程
    Java 教程

    共578课时 | 46.3万人学习

    关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
    php中文网:公益在线php培训,帮助PHP学习者快速成长!
    关注服务号 技术交流群
    PHP中文网订阅号
    每天精选资源文章推送

    Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号