ZetCode-Java-教程-二-
ZetCode Java 教程(二)
原文:ZetCode
Java 语法结构
原文:http://zetcode.com/lang/java/lexis/
像人类语言一样,计算机语言也具有词汇结构。 Java 程序的源代码由令牌组成。 令牌是原子代码元素。 在 Java 中,我们具有注释,标识符,字面值,运算符,分隔符和关键字。
Java 程序由 Unicode 字符集中的字符组成。
Java 注释
注释被人类用来阐明源代码。 Java 中有三种类型的注释。
注释类型 | 含义 |
---|---|
//注释 |
单行注释 |
/ *注释* / |
多行注释 |
/ **文档* / |
文档注释 |
如果要添加一些小注释,可以使用单行注释。 对于更复杂的解释,我们可以使用多行注释。 文档注释用于准备自动生成的文档。 这是通过javadoc
工具生成的。
com/zetcode/Comments.java
package com.zetcode;
/*
This is Comments.java
Author: Jan Bodnar
ZetCode 2017
*/
public class Comments {
// Program starts here
public static void main(String[] args) {
System.out.println("This is Comments.java");
}
}
该程序使用两种类型的注释。
// Program starts here
这是单行注释的示例。
Java 编译器将忽略注释。
/*
This is Comments.java
/* Author: Jan Bodnar */
ZetCode 2017
*/
注释不能嵌套。 上面的代码无法编译。
Java 空白字符
Java 中的空格用于分隔源文件中的标记。 它还用于提高源代码的可读性。
int i = 0;
在某些地方需要空格。 例如,在int
关键字和变量名之间。 在其他地方,禁止使用空格。 它们不能出现在变量标识符或语言关键字中。
int a=1;
int b = 2;
int c = 3;
标记之间放置的空间量与 Java 编译器无关。 在 Java 源代码中应始终使用空格。
Java 标识符
标识符是变量,方法,类或参数的名称。 标识符可以包含字母数字字符,下划线和美元符号($)。 以数字开头的变量名称是错误的。 名称中不允许使用空格。
标识符区分大小写。 这意味着Name
,name
或NAME
引用了三个不同的变量。 标识符也不能与语言关键字匹配。
还有一些与标识符命名有关的约定。 名称应具有描述性。 我们不应该将隐名用作标识符。 如果名称由多个单词组成,则随后的每个单词均大写。
以下是有效的 Java 标识符。
String name23;
int _col;
short car_age;
以下是无效的 Java 标识符。
String 23name;
int %col;
short car age;
以下程序演示了变量名区分大小写。 尽管语言允许这样做,但不建议这样做。
com/zetcode/CaseSensitiveIdentifiers.java
package com.zetcode;
public class CaseSensitiveIdentifiers {
public static void main(String[] args) {
String name = "Robert";
String Name = "Julia";
System.out.println(name);
System.out.println(Name);
}
}
在 Java 中, Name
和name
是两个不同的标识符。 而在 Visual Basic 中,他们是两个相同的标识符,因为 Visual Basic 的变量名不区分大小写。
$ java com.zetcode.CaseSensitiveIdentifiers
Robert
Julia
Java 字面值
字面值是类型的特定值的字面值表示。 字面值类型包括布尔值,整数,浮点数,字符串,空值或字符。 从技术上讲,字面值将在编译时分配一个值,而变量将在运行时分配。
int age = 29;
String nationality = "Hungarian";
在这里,我们为age
和nationality
两个变量分配了两个字面值。 数字 29 和字符串"Hungarian"
是字面值。
com/zetcode/Literals.java
package com.zetcode;
public class Literals {
public static void main(String[] args) {
int age = 23;
String name = "James";
boolean sng = true;
String job = null;
double weight = 68.5;
char c = 'J';
System.out.format("His name is %s%n", name);
System.out.format("His is %d years old%n", age);
if (sng) {
System.out.println("He is single");
} else {
System.out.println("He is in a relationship");
}
System.out.format("His job is %s%n", job);
System.out.format("He weighs %f kilograms%n", weight);
System.out.format("His name begins with %c%n", c);
}
}
在上面的示例中,我们有几个字面值。 23 是整数字面值。 "James"
是字符串字面值。 true
是布尔字面值。 null
是表示‘空’的字面值。 68.5 是浮点字面值。 'J'
是字符字面值。
$ java com.zetcode.Literals
His name is James
His is 23 years old
He is single
His job is null
He weighs 68.500000 kilograms
His name begins with J
这是程序的输出。
Java 运算符
运算符是用于对某个值执行操作的符号。 表达式中使用运算符来描述涉及一个或多个操作数的运算。
+ - * / % ^ & | ! ~
= += -= *= /= %= ^= ++ --
== != < > &= >>= <<= >= <=
|| && >> << ?:
这是 Java 运算符的部分列表。 我们将在本教程的后面部分讨论运算符。
Java 分隔符
分隔符是一个或多个字符的序列,用于指定纯文本或其他数据流中单独的独立区域之间的边界。
[ ] ( ) { } , ; . "
String language = "Java";
双引号用于标记字符串的开头和结尾。 分号;
字符用于结束每个 Java 语句。
System.out.println("Java language");
括号(圆括号)始终位于方法名称之后。 在括号之间,我们声明输入参数。 即使该方法不带任何参数,也会出现括号。 System.out.println()
方法采用一个参数,即字符串值。 点字符将类名(System
)与成员(out
)和方法名称(println()
)分开。
int[] array = new int[5] { 1, 2, 3, 4, 5 };
方括号[]
用于表示数组类型。 它们还用于访问或修改数组元素。 大括号{}
用于初始化数组。 大括号也用于包围方法或类的主体。
int a, b, c;
逗号字符在单个声明中分隔变量。
Java 关键字
关键字是 Java 语言中的保留字。 关键字用于在计算机程序中执行特定任务。 例如,要定义变量,执行重复性任务或执行逻辑运算。
Java 具有丰富的关键字。 其中许多内容将在本教程中进行解释。
abstract continue for new switch
assert default goto package synchronized
boolean do if private this
break double implements protected throw
byte else import public throws
case enum instanceof return transient
catch extends int short try
char final interface static var
class finally long strictfp void
const float native super volatile
while
在下面的小程序中,我们使用几个 Java 关键字。
com/zetcode/Keywords.java
package com.zetcode;
public class Keywords {
public static void main(String[] args) {
for (int i = 0; i <= 5; i++) {
System.out.println(i);
}
}
}
package
,public
,class
,static
,void
,int
和for
标记是 Java 关键字。
Java 约定
约定是程序员在编写源代码时遵循的最佳实践。 每种语言可以有自己的约定集。 约定不是严格的规则; 它们只是编写高质量代码的建议。 我们提到了 Java 程序员认可的一些约定。 (并且通常也被其他程序员使用)。
- 类名以大写字母开头。
- 方法名称以小写字母开头。
- 同时使用
public
关键字和static
关键字之前。 main()
方法的参数名称称为args
。- 常量以大写形式编写。
- 标识符名称中的每个后续单词均以大写字母开头。
在 Java 教程的这一部分中,我们介绍了 Java 语言的一些基本词汇。
Java RequestDispatcher
原文:http://zetcode.com/java/requestdispatcher/
Java RequestDispatcher
教程显示了如何使用 Java RequestDispatcher
将请求分派到资源。
RequestDispatcher
RequestDispatcher
从客户端接收请求,并将其发送到服务器上的资源(例如 Servlet,HTML 文件,JSP 文件,FreeMarker 或 Thymeleaf 模板)。
RequestDispatcher
方法
RequestDispatcher
有两种方法:
forward()
- 将请求从 Servlet 转发到另一个资源include()
- 在响应中包含资源的内容
两种方法之间的区别在于,forward()
方法将在调用后关闭输出流,而include()
方法将打开输出流。 include()
方法从另一个资源获取内容,并将其包含在 Servlet 中。 forward()
方法将请求发送到另一个资源。
获取RequestDispatcher
RequestDispatcher
可以从请求对象或 servlet 上下文中获得。
RequestDispatcher dispatcher = request.getRequestDispatcher("greet.jsp");
dispatcher.forward(request, response);
我们可以使用getRequestDispatcher()
方法从请求对象中获取RequestDispatcher
。
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/greet.jsp");
dispatcher.forward(request, response);
在这里,我们从 servlet 上下文中获取RequestDispatcher
。 在这种情况下,路径必须以斜杠字符开头。
Java RequestDispatcher
转发到 JSP
以下示例将来自客户端的请求发送到 JSP 页面。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Start Page</title>
<meta charset="UTF-8">
</head>
<body>
<form action="MyServlet">
<label>Enter your name:
<input type="text" name="name">
</label>
<button type="submit">Submit</button>
</form>
</body>
</html>
在主页中,我们有一个简单的形式:它从用户那里获取一个值,并将其作为请求参数发送到MyServlet
。
MyServlet.java
package com.zetcode.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "MyServlet", urlPatterns = {"/MyServlet"})
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
request.getRequestDispatcher("greet.jsp").forward(request, response);
}
}
在MyServlet
中,我们使用RequestDispatcher
转发到greet.jsp
页面。
greet.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSP Page</title>
</head>
<body>
<p>Hello ${param.name}!</p>
</body>
</html>
在greet.jsp
页面中,我们显示name
参数,该参数由用户在表单中设置。
Java RequestDispatcher
转发到 Servlet
以下示例将来自客户端的请求发送到 Servlet,该 Servlet 将处理转发到另一个 Servlet。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Start Page</title>
<meta charset="UTF-8">
</head>
<body>
<p>
<a href="MyServlet">Call servlet</a>
</p>
</body>
</html>
主页包含一个调用MyServlet
的链接。
MyServlet.java
package com.zetcode.web;
import java.io.IOException;
import java.time.Instant;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "MyServlet", urlPatterns = {"/MyServlet"})
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
request.setAttribute("now", Instant.now());
request.getRequestDispatcher("AnotherServlet").forward(request, response);
}
}
该请求首先到达MyServlet
。
request.setAttribute("now", Instant.now());
我们为请求设置一个属性; 这是当前时间。
request.getRequestDispatcher("AnotherServlet").forward(request, response);
包括新属性的请求被发送到AnotherServlet
。
AnotherServlet.java
package com.zetcode.web;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "AnotherServlet", urlPatterns = {"/AnotherServlet"})
public class AnotherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
PrintWriter out = response.getWriter();
DateTimeFormatter formatter
= DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.ENGLISH)
.withZone(ZoneId.of("UTC"));
Instant now = (Instant) request.getAttribute("now");
String output = formatter.format(now);
out.println(output);
}
}
AnotherServlet
将即时对象格式化为简短的英语日期时间格式,并将其打印到输出流中。
DateTimeFormatter formatter
= DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
.withLocale(Locale.ENGLISH)
.withZone(ZoneId.of("UTC"));
我们使用DateTimeFormatter
类格式化日期时间。
Instant now = (Instant) request.getAttribute("now");
我们使用getAttribute()
方法从请求中检索属性。
String output = formatter.format(now);
out.println(output);
即时被格式化并打印到输出中。
Java RequestDispatcher
包括
下一个示例包括从另一个 servlet 到调用 servlet 的输出。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Start Page</title>
<meta charset="UTF-8">
</head>
<body>
<p>
<a href="MyServlet">Call servlet</a>
</p>
</body>
</html>
主页包含一个调用MyServlet
的链接。
MyServlet.java
package com.zetcode.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "MyServlet", urlPatterns = {"/MyServlet"})
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("Hello from MyServlet");
request.getRequestDispatcher("AnotherServlet").include(request, response);
}
}
MyServlet
将数据打印到输出流并转发到AnotherServlet
。
AnotherServlet.java
package com.zetcode.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "AnotherServlet", urlPatterns = {"/AnotherServlet"})
public class AnotherServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println("Hello from AnotherServlet");
}
}
AnotherServlet
还将数据打印到输出流。 最后,我们将两条消息都写入输出流并发送给客户端。
在本教程中,我们介绍了 Java RequestDispatcher
。 我们已经介绍了RequestDispatcher
,forward
和include()
方法。 您可能还需要查看相关的教程: Java servlet JSON 教程,从 Java servlet , Java servlet 复选框教程,提供纯文本 Java Servlet 图像教程, Java Servlet HTTP 标头或 Java 教程,
{% raw %}
Java HTTP GET/POST 请求
原文:http://zetcode.com/java/getpostrequest/
本教程显示了如何使用 Java 发送 GET 和 POST 请求。 我们使用内置的HttpURLConnection
类以及标准的 Java 和 Apache HttpClient
类。
HTTP
超文本传输协议(HTTP)是用于分布式协作超媒体信息系统的应用协议。 HTTP 是万维网数据通信的基础。
在示例中,我们使用httpbin.org
(这是一个免费的 HTTP 请求和响应服务),以及webcode.me
(这是一个用于测试的小型 HTML 页面)。
HTTP GET
HTTP GET 方法请求指定资源的表示形式。 使用 GET 的请求应仅检索数据。
HTTP POST
HTTP POST 方法将数据发送到服务器。 在上载文件或提交完整的 Web 表单时,通常使用它。
Java 11 HttpClient
的 GET 请求
从 Java 11 开始,我们可以使用java.net.http.HttpClient
。
com/zetcode/GetRequestJava11.java
package com.zetcode;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class GetRequestJava11 {
public static void main(String[] args) throws IOException, InterruptedException {
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://webcode.me"))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
我们向webcode.me
网页创建 GET 请求。
HttpClient client = HttpClient.newHttpClient();
使用newHttpClient()
工厂方法创建一个新的HttpClient
。
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://webcode.me"))
.build();
我们建立对该网页的同步请求。 默认方法是 GET。
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
我们发送请求并检索响应的内容,然后将其打印到控制台。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My html page</title>
</head>
<body>
<p>
Today is a beautiful day. We go swimming and fishing.
</p>
<p>
Hello there. How are you?
</p>
</body>
</html>
这是输出。
Java 11 HttpClient
的 Java HTTP POST 请求
下一个示例使用 Java 11 HttpClient
创建 POST 请求。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.9.3</version>
</dependency>
我们需要jackson-databind
依赖项。
com/zetcode/PostRequestJava11.java
package com.zetcode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.HashMap;
public class HttpClientPost {
public static void main(String[] args) throws IOException, InterruptedException {
var values = new HashMap<String, String>() {{
put("name", "John Doe");
put ("occupation", "gardener");
}};
var objectMapper = new ObjectMapper();
String requestBody = objectMapper
.writeValueAsString(values);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
我们将 POST 请求发送到https://httpbin.org/post
页面。
var values = new HashMap<String, String>() {{
put("name", "John Doe");
put ("occupation", "gardener");
}};
var objectMapper = new ObjectMapper();
String requestBody = objectMapper
.writeValueAsString(values);
首先,我们使用 Jackson 的ObjectMapper
构建请求主体。
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://httpbin.org/post"))
.POST(HttpRequest.BodyPublishers.ofString(requestBody))
.build();
我们构建 POST 请求。 使用BodyPublishers.ofString()
创建一个新的BodyPublisher
。 它将高级 Java 对象转换为适合作为请求正文发送的字节缓冲区流。
HttpResponse<String> response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
我们发送请求并检索响应。
{
"args": {},
"data": "{\"occupation\":\"gardener\",\"name\":\"John Doe\"}",
"files": {},
"form": {},
"headers": {
"Content-Length": "43",
"Host": "httpbin.org",
"User-Agent": "Java-http-client/12.0.1"
},
"json": {
"name": "John Doe",
"occupation": "gardener"
},
...
"url": "https://httpbin.org/post"
}
这是输出。
使用HttpURLConnection
的 Java HTTP GET 请求
以下示例使用HttpURLConnection
创建 GET 请求。
com/zetcode/JavaGetRequest.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class JavaGetRequest {
private static HttpURLConnection con;
public static void main(String[] args) throws IOException {
var url = "http://webcode.me";
try {
var myurl = new URL(url);
con = (HttpURLConnection) myurl.openConnection();
con.setRequestMethod("GET");
StringBuilder content;
try (BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()))) {
String line;
content = new StringBuilder();
while ((line = in.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
}
System.out.println(content.toString());
} finally {
con.disconnect();
}
}
}
该示例使用 HTTP GET 请求检索网页。
var url = "http://webcode.me";
我们检索此小型网页的内容。
var myurl = new URL(url);
con = (HttpURLConnection) myurl.openConnection();
创建到指定 URL 的连接。
con.setRequestMethod("GET");
我们使用setRequestMethod()
方法设置请求方法类型。
try (BufferedReader in = new BufferedReader(
new InputStreamReader(con.getInputStream()))) {
输入流是从 HTTP 连接对象创建的。 输入流用于读取返回的数据。
content = new StringBuilder();
我们使用StringBuilder
构建内容字符串。
while ((line = in.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
我们使用readLine()
逐行从输入流中读取数据。 每行都添加到StringBuilder
中。 在每行之后,我们附加一个与系统有关的行分隔符。
System.out.println(content.toString());
我们将内容打印到终端。
使用HttpURLConnection
的 Java HTTP POST 请求
以下示例使用HttpURLConnection
创建 POST 请求。
com/zetcode/JavaPostRequest.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class JavaPostRequest {
private static HttpURLConnection con;
public static void main(String[] args) throws IOException {
var url = "https://httpbin.org/post";
var urlParameters = "name=Jack&occupation=programmer";
byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
try {
var myurl = new URL(url);
con = (HttpURLConnection) myurl.openConnection();
con.setDoOutput(true);
con.setRequestMethod("POST");
con.setRequestProperty("User-Agent", "Java client");
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
try (var wr = new DataOutputStream(con.getOutputStream())) {
wr.write(postData);
}
StringBuilder content;
try (var br = new BufferedReader(
new InputStreamReader(con.getInputStream()))) {
String line;
content = new StringBuilder();
while ((line = br.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
}
System.out.println(content.toString());
} finally {
con.disconnect();
}
}
}
该示例将 POST 请求发送到https://httpbin.org/post
。
var urlParameters = "name=Jack&occupation=programmer";
byte[] postData = urlParameters.getBytes(StandardCharsets.UTF_8);
我们将编写这两个键/值对。 我们将字符串转换为字节数组。
var myurl = new URL(url);
con = (HttpURLConnection) myurl.openConnection();
URL 的连接已打开。
con.setDoOutput(true);
通过setDoOutput()
方法,我们指示我们将数据写入 URL 连接。
con.setRequestMethod("POST");
HTTP 请求类型通过setRequestMethod()
设置。
con.setRequestProperty("User-Agent", "Java client");
我们使用setRequestProperty()
方法设置用户年龄属性。
try (DataOutputStream wr = new DataOutputStream(con.getOutputStream())) {
wr.write(postData);
}
我们将字节或数据写入 URL 连接。
StringBuilder content;
try (var br = new BufferedReader(
new InputStreamReader(con.getInputStream()))) {
String line;
content = new StringBuilder();
while ((line = br.readLine()) != null) {
content.append(line);
content.append(System.lineSeparator());
}
}
System.out.println(content.toString());
我们读取连接的输入流,并将检索到的内容写入控制台。
使用 Apache HttpClient
的 Java HTTP GET 请求
以下示例使用 Apache HttpClient
创建 GET 请求。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
对于示例,我们需要此 Maven 依赖关系。
com/zetcode/ApacheHttpClientGet.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
public class ApacheHttpClientGet {
public static void main(String[] args) throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
var request = new HttpGet("http://webcode.me");
HttpResponse response = client.execute(request);
var bufReader = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
var builder = new StringBuilder();
String line;
while ((line = bufReader.readLine()) != null) {
builder.append(line);
builder.append(System.lineSeparator());
}
System.out.println(builder);
}
}
}
该示例发送 GET 请求以读取指定网页的主页。
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
CloseableHttpClient
是使用HttpClientBuilder
构建的。
var request = new HttpGet("http://webcode.me");
HttpGet
用于创建 HTTP GET 请求。
HttpResponse response = client.execute(request);
我们执行请求并获得响应。
var bufReader = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
从响应对象中,我们读取内容。
while ((line = bufReader.readLine()) != null) {
builder.append(line);
builder.append(System.lineSeparator());
}
我们逐行读取内容并动态生成字符串消息。
Java HTTP POST 与 Apache HttpClient
以下示例使用HttpPost
创建 POST 请求。
com/zetcode/ApacheHttpClientPost.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
public class ApacheHttpClientPost {
public static void main(String[] args) throws IOException {
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
var request = new HttpPost("https://httpbin.org/post");
request.setHeader("User-Agent", "Java client");
request.setEntity(new StringEntity("My test data"));
HttpResponse response = client.execute(request);
var bufReader = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
var builder = new StringBuilder();
String line;
while ((line = bufReader.readLine()) != null) {
builder.append(line);
builder.append(System.lineSeparator());
}
System.out.println(builder);
}
}
}
The example sends a POST request to https://httpbin.org/post
.
var request = new HttpPost("https://httpbin.org/post");
HttpPost
用于创建 POST 请求。
request.setEntity(new StringEntity("My test data"));
用setEntity()
方法设置数据。
request.setHeader("User-Agent", "Java client");
我们使用setHeader()
方法为请求设置标头。
HttpResponse response = client.execute(request);
我们执行请求并获得响应。
var bufReader = new BufferedReader(new InputStreamReader(
response.getEntity().getContent()));
var builder = new StringBuilder();
String line;
while ((line = bufReader.readLine()) != null) {
builder.append(line);
builder.append(System.lineSeparator());
}
System.out.println(builder);
我们阅读响应并将其打印到终端。
在本教程中,我们使用HttpURLConnection
以及标准 Java 和 Apache HttpClient
在 Java 中创建了 GET 和 POST 请求。
您可能也对以下相关教程感兴趣: Python 请求教程, Jsoup 教程, Java 教程,用 Java 读取网页或 Google Guava 简介。
列出所有 Java 教程。
{% endraw %}
Java InputStream
教程
原文:http://zetcode.com/java/inputstream/
Java InputStream
教程显示了如何使用 Java 中的InputStream
类。
Java 流是来自源或目的地的数据流。 Java 流的一个很好的比喻是水从水龙头流入浴缸,然后又流入排水装置。 InputStream
和OutputStream
是对数据(例如 C 文件指针)的低级访问的抽象。
Java InputStream
InputStream
是读取数据的源。 流可以表示各种类型的源,包括磁盘文件,设备,其他程序和内存数组。
流支持许多不同类型的数据,包括简单字节,原始数据类型,本地化字符和对象。
Java InputStream
子类
InputStream
是一个抽象类; 它是表示字节输入流的所有类的超类,包括AudioInputStream
,ByteArrayInputStream
,FileInputStream
,FilterInputStream
,ObjectInputStream
,PipedInputStream
和SequenceInputStream
。
Java InputStream
关闭
FileInputStream
的close()
方法关闭输入流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用try-with-resources
语句,该语句确保在语句末尾关闭每个资源。
Java InputStream
读取
InputStream
使用以下读取方法读取字节:
read(byte[] b)
- 从此输入流中读取最多b.length
个字节的数据到一个字节数组中。read(byte[] b, int off, int len)
- 从此输入流中读取最多len
个字节的数据到一个字节数组中。read()
- 从文件输入流中读取一个字节。
Java InputStream
读取文本
以下示例显示如何使用InputStream
读取文本文件。
thermopylae.txt
The Battle of Thermopylae was fought between an alliance of Greek city-states,
led by King Leonidas of Sparta, and the Persian Empire of Xerxes I over the
course of three days, during the second Persian invasion of Greece.
在示例中,我们使用此文本文件。
JavaInputStreamText.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class JavaInputStreamText {
public static void main(String[] args) throws IOException {
String fileName = "src/resources/thermopylae.txt";
try (InputStream fis = new FileInputStream(fileName);
InputStreamReader isr = new InputStreamReader(fis,
StandardCharsets.UTF_8);
BufferedReader br = new BufferedReader(isr)) {
br.lines().forEach(line -> System.out.println(line));
}
}
}
使用FileInputStream
,InputStreamReader
和BufferedReader
读取文本文件。
try (InputStream fis = new FileInputStream(fileName);
FileInputStream
是InputStream
的一种特殊形式,用于从文件中读取字节。
InputStreamReader isr = new InputStreamReader(fis,
StandardCharsets.UTF_8);
InputStreamReader
是从字节流到字符流的桥梁:它读取字节,并使用指定的字符集将其解码为字符。
BufferedReader br = new BufferedReader(isr)) {
BufferedReader
从字符输入流中读取文本,缓冲字符以有效读取字符,数组和行。
br.lines().forEach(line -> System.out.println(line));
从缓冲读取器中按行读取数据。
Java InputStream
读取字节
InputStream
的读取方法读取字节。
JavaInputStreamBytes.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class JavaInputStreamBytes {
public static void main(String[] args) throws IOException {
String fileName = "src/resources/ball.png";
try (InputStream is = new FileInputStream(fileName)) {
byte[] buffer = new byte[is.available()];
is.read(buffer);
int i = 0;
for (byte b: buffer) {
if (i % 10 == 0) {
System.out.println();
}
System.out.printf("%02x ", b);
i++;
}
}
System.out.println();
}
}
该示例从 PNG 图像读取字节,并将字节以十六进制格式打印到控制台。
try (InputStream is = new FileInputStream(fileName)) {
我们使用FileInputStream
从图像文件中读取字节。
byte[] buffer = new byte[is.available()];
is.read(buffer);
使用read()
方法,我们将字节读入字节数组。
int i = 0;
for (byte b: buffer) {
if (i % 10 == 0) {
System.out.println();
}
System.out.printf("%02x ", b);
i++;
}
我们遍历数组并将字节以十六进制格式打印到控制台。
89 50 4e 47 0d 0a 1a 0a 00 00
00 0d 49 48 44 52 00 00 00 0a
00 00 00 0a 08 06 00 00 00 8d
32 cf bd 00 00 00 04 73 42 49
54 08 08 08 08 7c 08 64 88 00
00 00 09 70 48 59 73 00 00 0d
d7 00 00 0d d7 01 42 28 9b 78
00 00 00 19 74 45 58 74 53 6f
...
这是示例的部分示例输出。
从 URL 读取 Java InputStream
InputStream
允许从 URL 源读取数据。
JavaInputStreamURL.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class JavaInputStreamURL {
public static void main(String[] args) throws IOException {
String webSite = "http://www.something.com";
URL url = new URL(webSite);
try (InputStream is = url.openStream();
BufferedReader br = new BufferedReader(
new InputStreamReader(is))) {
br.lines().forEach(System.out::println);
}
}
}
该示例将InputStream
打开到网页并读取其数据。
try (InputStream is = url.openStream();
使用openStream()
方法创建 URL 的InputStream
。
<html><head><title>Something.</title></head>
<body>Something.</body>
</html>
这是输出。
Java InputStream
读取反序列化的数据
ObjectInputStream
读取先前使用ObjectOutputStream
写入的序列化数据。
Country.java
package com.zetcode;
import java.io.Serializable;
public class Country implements Serializable {
static final long serialVersionUID = 42L;
private String name;
private int population;
public Country(String name, int population) {
this.name = name;
this.population = population;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPopulation() {
return population;
}
public void setPopulation(int population) {
this.population = population;
}
}
这是Country
bean。 我们将序列化和反序列化国家列表。
JavaObjectOutputStreamEx.java
package com.zetcode;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
public class JavaObjectOutputStreamEx {
public static void main(String[] args) throws IOException {
String fileName = "src/resources/myfile.dat";
try (OutputStream fis = new FileOutputStream(fileName);
ObjectOutputStream out = new ObjectOutputStream(fis)) {
List<Country> countries = new ArrayList<>();
countries.add(new Country("Slovakia", 5429000));
countries.add(new Country("Norway", 5271000));
countries.add(new Country("Croatia", 4225000));
countries.add(new Country("Russia", 143439000));
out.writeObject(countries);
}
}
}
该示例序列化对象列表。
out.writeObject(countries);
国家列表被写入ObjectOutputStream
。
JavaInputStreamObjects.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.util.List;
public class JavaInputStreamObjects {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName = "src/resources/myfile.dat";
try (InputStream fis = new FileInputStream(fileName);
ObjectInputStream oin = new ObjectInputStream(fis)) {
List<Country> countries = (List<Country>) oin.readObject();
countries.forEach(System.out::println);
}
}
}
我们使用ObjectInputStream
读取序列化的数据。
Java InputStream
读取流序列
SequenceInputStream
代表一系列输入流。 它允许从多个有序流中读取。
JavaInputStreamSequence.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.SequenceInputStream;
public class JavaInputStreamSequence {
public static void main(String[] args) throws IOException {
String fileName1 = "src/resources/myfile.txt";
String fileName2 = "src/resources/myfile1.txt";
String fileName3 = "src/resources/myfile2.txt";
try (InputStream is1 = new FileInputStream(fileName1);
InputStream is2 = new FileInputStream(fileName2);
InputStream is3 = new FileInputStream(fileName3);
SequenceInputStream sis1 = new SequenceInputStream(is1, is2);
SequenceInputStream sis = new SequenceInputStream(sis1, is3)) {
int b = sis.read();
while (b != -1) {
System.out.printf("%c", b);
b = sis.read();
}
System.out.println();
}
}
}
该示例从三个FileInputStreams
中读取。
try (InputStream is1 = new FileInputStream(fileName1);
InputStream is2 = new FileInputStream(fileName2);
InputStream is3 = new FileInputStream(fileName3);
SequenceInputStream sis1 = new SequenceInputStream(is1, is2);
SequenceInputStream sis = new SequenceInputStream(sis1, is3)) {
我们定义了三个输入流,并将这些流放入SequenceInputStreams
中。
int b = sis.read();
while (b != -1) {
System.out.printf("%c", b);
b = sis.read();
}
我们使用read()
从流中读取数据。
在本教程中,我们介绍了 Java InputStream
类。 您可能也对相关教程感兴趣: Java InputStreamReader
教程, Java FileOutputStream
教程, Java FileInputStream
教程, Java 文件时间, Java FileWriter
教程, Java 附加到文件,用 Java 读取文本文件和 Java 教程。
Java FileOutputStream
教程
原文:http://zetcode.com/java/fileoutputstream/
Java FileOutputStream
教程显示了如何使用FileOutputStream
类写入 Java 中的文件。
Java FileOutputStream
FileOutputStream
是用于将数据写入File
或FileDescriptor
的输出流。 FileOutputStream
是OutputStream
的子类,它接受输出字节并将其发送到某个接收器。 在FileOutputStream
的情况下,接收器是文件对象。
Java FileOutputStream
构造器
这些是FileOutputStream
构造器:
FileOutputStream(File file)
- 创建文件输出流以写入File
对象。FileOutputStream(File file, boolean append)
- 创建文件输出流以写入File
对象; 允许附加模式。FileOutputStream(FileDescriptor fdObj)
- 创建文件输出流以写入指定的文件描述符。FileOutputStream(String name)
- 创建文件输出流以写入具有指定名称的文件。FileOutputStream(String name, boolean append)
- 创建文件输出流以写入具有指定名称的文件; 允许附加模式。
Java FileOutputStream
关闭
FileOutputStream
的close()
方法关闭文件输出流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用try-with-resources
语句,该语句确保在语句末尾关闭每个资源。
Java FileOutputStream
写入
FileOutputStream
使用以下写入方法写入字节:
write(byte[] b)
- 将字节数组写入文件输出流。write(byte[] b, int off, int len)
- 从指定的字节数组开始将len
个字节从offset
偏移量写入文件输出流。write(int b)
- 将一个字节写入文件输出流。
Java FileOutputStream
示例
以下示例使用FileOutputStream
将文本写入文件。
FileOutputStreamEx.java
package com.zetcode;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamEx {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/newfile.txt";
try (FileOutputStream fos = new FileOutputStream(fileName)) {
String text = "Today is a beautiful day";
byte[] mybytes = text.getBytes();
fos.write(mybytes);
}
}
}
该代码示例将一行写入文件。
try (FileOutputStream fos = new FileOutputStream(fileName)) {
FileOutputStream
构造器采用字符串作为参数; 它是我们写入的文件名。 完成编写后,我们使用try-with-resources
构造来清理资源。
String text = "Today is a beautiful day";
byte[] mybytes = text.getBytes();
FileOutputStream
将字节写入文件; 我们使用getBytes()
方法从字符串中获取字节。
fos.write(mybytes);
字节被写入文件。
$ cat newfile.txt
Today is a beautiful day
我们使用cat
命令显示文件的内容。
Java FileOutputStream
附加到文件
使用FileOutputStream
可以将数据附加到文件中。 附加的典型用法是日志记录。
FileOutputStreamAppend.java
package com.zetcode;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class FileOutputStreamAppend {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/newfile.txt";
try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
String text = "Today is a beautiful day";
byte[] mybytes = text.getBytes();
fos.write(mybytes);
}
}
}
该代码示例将文本附加到文件。
try (FileOutputStream fos = new FileOutputStream(fileName, true)) {
FileOutputStream
的第二个参数表示我们将附加到文件中。
Java FileOutputStream
指定编码
FileWriter
类是用于编写字符文件的 Java 便利类,它有一个严重的限制:它使用默认编码,并且不允许我们显式指定编码。 如果必须设置编码,则可以使用OutputStreamWriter
和FileOutputStream
。
FileOutputStreamEncoding.java
package com.zetcode;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class FileOutputStreamEncoding {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/newfile.txt";
FileOutputStream fos = new FileOutputStream(fileName);
try (OutputStreamWriter osw = new OutputStreamWriter(fos,
StandardCharsets.UTF_8)) {
String text = "Сегодня был прекрасный день.";
osw.write(text);
}
}
}
该示例使用OutputStreamWriter
将文本写入文件。 第二个参数是要使用的字符集。
$ cat newwfile.txt
Сегодня был прекрасный день.
我们显示文件的内容。
在本教程中,我们介绍了 Java FileOutputStream
类。 您可能也对相关教程感兴趣: Java FileInputStream
教程, Java InputStream
教程, Java 谓词教程, Java 文件时间, Java FileWriter
教程, Java 附加到文件,用 Java 读取文本文件,用 Java 读写 ICO 图像 和 Java 教程。
Java FileInputStream
教程
原文:http://zetcode.com/java/fileinputstream/
Java FileInputStream
教程显示了如何使用FileInputStream
类读取 Java 中的文件。
Java FileInputStream
FileInputStream
从文件系统中的文件读取输入字节。
Java FileInputStream
构造器
这些是FileInputStream
构造器:
FileInputStream(File file)
- 创建文件输入流以从File
对象读取。FileInputStream(String name)
- 创建文件输入流以从指定的文件名读取。FileInputStream(FileDescriptor fdObj)
- 创建从指定文件描述符读取的文件输入。
Java FileInputStream
关闭
FileInputStream
的close()
方法关闭文件输入流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用try-with-resources
语句,该语句确保在语句末尾关闭每个资源。
Java FileInputStream
读取
FileInputStream
使用以下读取方法读取字节:
read(byte[] b)
-从此输入流中读取最多b.length
个字节的数据到一个字节数组中。read(byte[] b, int off, int len)
-从此输入流中读取最多len
个字节的数据到一个字节数组中。read()
-从文件输入流中读取一个字节。
Java FileInputStream
读取字符
以下示例使用FileInputStream
从文件中读取三个字符。
FileInputStreamEx.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamEx {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/smallfile.txt";
try (FileInputStream fis = new FileInputStream(fileName)) {
char c1 = (char) fis.read();
char c2 = (char) fis.read();
char c3 = (char) fis.read();
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
}
}
}
该代码示例使用read()
读取三个字符。
char c1 = (char) fis.read();
我们用read()
读取一个字符,并将该值转换为char
。
System.out.println(c1);
字符被打印到控制台。
Java FileInputStream
按字符读取文件
如果到达文件末尾,则read()
方法返回 -1。 通过while
循环,我们可以逐字符读取整个文件。 请注意,这种方式不是很有效。
FileInputStreamEx2.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class FileInputStreamEx2 {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/smallfile.txt";
try (FileInputStream fis = new FileInputStream(fileName)) {
int i;
while ((i = fis.read()) != -1) {
System.out.print((char) i);
}
}
System.out.println();
}
}
该示例读取文件的内容并将其写入终端。
while ((i = fis.read()) != -1) {
System.out.print((char) i);
}
在while
循环中,我们从FileInputStream
读取字符,直到read()
方法返回 -1。
Java FileInputStream
通过文本块读取文件
按数据块读取文件更有效; 例如每个方法调用中 1024 个字节。
FileInputStreamEx3.java
package com.zetcode;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class FileInputStreamEx3 {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/bigfile.txt";
try (FileInputStream fis = new FileInputStream(fileName)) {
int i = 0;
do {
byte[] buf = new byte[1024];
i = fis.read(buf);
String value = new String(buf, StandardCharsets.UTF_8);
System.out.print(value);
} while (i != -1);
}
}
}
在此示例中,我们通过数据块读取文件
byte[] buf = new byte[1024];
我们从文件中读取数据到此字节数组中。
i = fis.read(buf);
read()
方法从此流中最多读取b.length
个字节的数据到提供的字节数组中。
String value = new String(buf, StandardCharsets.UTF_8);
从字节数组中,我们创建一个String
。
使用BufferedReader
的 Java FileInputStream
使用BufferedReader
可以提高阅读效率。 BufferedReader
从字符输入流中读取文本,缓冲字符,以便有效读取字符。
FileInputStreamEx4.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
public class FileInputStreamEx4 {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "/home/janbodnar/tmp/bigfile.txt";
try (BufferedReader br = new BufferedReader(new InputStreamReader(
new FileInputStream(fileName), StandardCharsets.UTF_8));) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}
System.out.println();
}
}
该示例使用缓冲技术读取大文件,以提高效率。
while ((line = br.readLine()) != null) {
readLine()
方法从缓冲区读取一行文本。
在本教程中,我们介绍了 Java FileInputStream
类。 您可能也对相关教程感兴趣: Java FileOutputStream
教程, Java InputStreamReader
教程, Java InputStream
教程, Java 谓词教程, Java 文件时间, Java 附加到文件,用 Java 读取文本文件和 Java 教程。
Java ZipInputStream
教程
原文:http://zetcode.com/java/zipinputstream/
Java ZipInputStream
教程显示了如何使用ZipInputStream
读取 Java 中的 ZIP 文件。
Java ZipInputStream
ZipInputStream
是 Java 类,实现用于读取 ZIP 文件格式的文件的输入流过滤器。 它支持压缩和未压缩的条目。
ZIP
ZIP 是一种存档文件格式,支持无损数据压缩。 一个 ZIP 文件可能包含一个或多个已压缩的文件或目录。 Java Archive(JAR)建立在 ZIP 格式上。
ZipInputStream
构造器
ZipInputStream
具有以下构造器:
ZipInputStream(InputStream in)
ZipInputStream(InputStream in, Charset charset)
ZipInputStream getNextEntry
ZipInputStream
的getNextEntry()
读取下一个 ZIP 文件条目,并将流定位在条目数据的开头。
Java 读取 ZIP 示例
下面的示例读取一个 ZIP 文件的内容。
JavaReadZip.java
package com.zetcode;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.time.LocalDate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class JavaReadZip {
private final static Long MILLS_IN_DAY = 86400000L;
public static void main(String[] args) throws IOException {
String fileName = "src/resources/myfile.zip";
try (FileInputStream fis = new FileInputStream(fileName);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream zis = new ZipInputStream(bis)) {
ZipEntry ze;
while ((ze = zis.getNextEntry()) != null) {
System.out.format("File: %s Size: %d Last Modified %s %n",
ze.getName(), ze.getSize(),
LocalDate.ofEpochDay(ze.getTime() / MILLS_IN_DAY));
}
}
}
}
该示例使用ZipInputStream
读取给定的 ZIP 文件,并将其内容打印到终端。 我们打印文件名,文件大小和最后修改时间。
String fileName = "src/resources/myfile.zip";
ZIP 文件位于src/resources/
目录中。
try (FileInputStream fis = new FileInputStream(fileName);
我们从文件创建一个FileInputStream
。 FileInputStream
用于读取原始字节流。
BufferedInputStream bis = new BufferedInputStream(fis);
为了获得更好的性能,我们将FileInputStream
传递到BufferedInputStream
中。
ZipInputStream zis = new ZipInputStream(bis)) {
ZipInputStream
是从缓冲的FileInputStream
创建的。 当资源不再需要时,try-with-resources
将关闭流。
while ((ze = zis.getNextEntry()) != null) {
在while
循环中,我们使用getNextEntry()
方法浏览 ZIP 文件的条目。 如果没有更多条目,则返回null
。
System.out.format("File: %s Size: %d Last Modified %s %n",
ze.getName(), ze.getSize(),
LocalDate.ofEpochDay(ze.getTime() / MILLS_IN_DAY));
getName()
返回条目的名称,getSize()
返回条目的未压缩大小,getTime()
返回条目的最后修改时间。
File: maven.pdf Size: 6430817 Last Modified 2017-02-23
File: mavenbyexample.pdf Size: 1363061 Last Modified 2017-02-15
File: modal_verbs.jpg Size: 31353 Last Modified 2017-03-04
File: sid.jpg Size: 57708 Last Modified 2017-06-05
File: spring-boot-reference.pdf Size: 1946586 Last Modified 2017-06-05
这是一个示例输出。
Java 解压缩 ZIP 示例
在下一个示例中,我们用 Java 解压缩 ZIP 文件。
JavaUnzip.java
package com.zetcode;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class JavaUnzip {
public static void main(String args[]) throws Exception {
byte[] buffer = new byte[2048];
Path outDir = Paths.get("src/resources/output/");
String zipFileName = "src/resources/myfile.zip";
try (FileInputStream fis = new FileInputStream(zipFileName);
BufferedInputStream bis = new BufferedInputStream(fis);
ZipInputStream stream = new ZipInputStream(bis)) {
ZipEntry entry;
while ((entry = stream.getNextEntry()) != null) {
Path filePath = outDir.resolve(entry.getName());
try (FileOutputStream fos = new FileOutputStream(filePath.toFile());
BufferedOutputStream bos = new BufferedOutputStream(fos, buffer.length)) {
int len;
while ((len = stream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
}
}
}
}
}
该示例使用ZipInputStream
读取给定 ZIP 文件的内容,并使用FileOutputStream
和BufferedOutputStream
将该内容写入目录。
Path outDir = Paths.get("src/resources/output/");
这是我们提取 ZIP 文件内容的目录。
while ((entry = stream.getNextEntry()) != null) {
在第一个while
循环中,我们浏览 ZIP 文件的条目。
while ((len = stream.read(buffer)) > 0) {
bos.write(buffer, 0, len);
}
在第二个while
循环中,我们读取条目并将其写入输出流。
在本教程中,我们介绍了 Java ZipInputStream
类。 我们创建了两个示例来读取 ZIP 文件和解压缩 ZIP 文件。 您可能也对相关教程感兴趣:Java 读取文本文件, Java HashMap
教程, Java ArrayList
教程, Java static
关键字, Java 中的HashMap
迭代, Java8 forEach
教程,用 Java 读写 ICO 图像, Java 教程, 用 Java 显示图像。
Java FileWriter
教程
原文:http://zetcode.com/java/filewriter/
Java FileWriter
教程显示了如何使用FileWriter
类将文本写入 Java 文件。 请注意,FileWriter
有一个严重的限制:它使用默认编码,并且不允许我们显式指定编码。
Java FileWriter
FileWriter
是 Java 便利类,用于将文本数据写入文件。 FileWriter
扩展了OutputStreamWriter
并创建了FileOutputStream
。
Java FileWriter
构造器
这些是FileWriter
构造器:
FileWriter(File file)
— 将FileWriter
构造为File
对象。FileWriter(File file, boolean append)
- 将FileWriter
对象构造为File
对象; 允许附加模式。FileWriter(FileDescriptor fd)
- 将FileWriter
构造为FileDescriptor
。FileWriter(String fileName)
- 将FileWriter
构造为文件名。FileWriter(String fileName, boolean append)
- 将FileWriter
对象构造为文件名; 允许附加模式。
Java FileWriter
写入文件
使用FileInputStream
和FileOutputStream
,我们创建用于读取和写入File
的流。 找不到文件时,将引发FileNotFoundException
。 File
是 Java 中文件或目录的表示。
JavaFileWriterEx.java
package com.zetcode;
import java.io.FileWriter;
import java.io.IOException;
public class JavaFileWriterEx {
public static void main(String[] args) throws IOException {
try (FileWriter writer = new FileWriter("src/resources/myfile.txt")) {
writer.write("Today is a sunny day");
}
}
}
该示例使用FileWriter
将文本数据写入文件。
try (FileWriter writer = new FileWriter("src/resources/myfile.txt")) {
FileWriter
构造器采用字符串作为参数; 它是我们写入的文件名。 完成编写后,我们使用try-with-resources
构造来清理资源。
writer.write("Today is a sunny day");
FileWriter
的write()
方法将文本写入文件。
Java FileWriter
附加到文件
使用FileWriter
可以将文本附加到文件中。 附加的典型用法是日志记录。
JavaFileWritterAppend.java
package com.zetcode;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
public class JavaFileWritterAppend {
public static void main(String[] args) throws IOException {
String fileName = "src/resources/myfile.txt";
File myfile = new File(fileName);
try (FileWriter writer = new FileWriter(myfile, true)) {
writer.write("Tomorrow will be cloudy.");
}
}
}
该代码示例将文本附加到文件。
try (FileWriter writer = new FileWriter(myfile, true)) {
FileWriter
的第二个参数告诉我们将附加到文件中。
使用BufferedWriter
的FileWriter
BufferedWriter
可以提高FileWriter's
性能。 BufferedWriter
将文本写入字符输出流,缓冲字符以提高写入单个字符,数组和字符串的性能。 可以指定缓冲区大小,也可以接受默认大小; 默认值对于大多数用途来说足够大。
JavaFileWriterBuffered.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
public class JavaFileWriterBuffered {
public static void main(String[] args) throws MalformedURLException, IOException {
String text = readText();
String fileName = "src/resources/wikipedia_home_page.txt";
try (FileWriter writer = new FileWriter(fileName);
BufferedWriter bufWriter = new BufferedWriter(writer)) {
bufWriter.write(text);
}
}
public static String readText() throws MalformedURLException, IOException {
StringBuilder sb;
URL url = new URL("https://www.wikipedia.org");
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
String line;
sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line);
sb.append(System.lineSeparator());
}
}
return sb.toString();
}
}
在示例中,我们阅读了维基百科的主页(其 HTML 代码)并将其写入文件。 主页足够大以考虑缓冲。
try (FileWriter writer = new FileWriter(fileName);
BufferedWriter bufWriter = new BufferedWriter(writer)) {
bufWriter.write(text);
}
FileWriter
作为参数传递给BufferedWriter
。 然后,我们调用BufferedWriter
的write()
方法来编写文本。
try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
读取操作也通过BufferedReader
类进行缓冲。
指定编码
FileWriter
使用默认编码,并且不允许我们显式指定编码。 如果必须设置编码,则可以使用OutputStreamWriter
和FileOutputStream
。
JavaFileOutputStreamEx.java
package com.zetcode;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
public class JavaFileOutputStreamEx {
public static void main(String[] args) throws FileNotFoundException, IOException {
String fileName = "src/resources/myfile.txt";
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(fileName),
StandardCharsets.UTF_8)) {
osw.write("Сегодня был прекрасный день.");
}
}
}
该示例使用OutputStreamWriter
将文本写入文件。 第二个参数是要使用的字符集。
在本教程中,我们介绍了 Java FileWriter
类。 您可能也对相关教程感兴趣: Java FileOutputStream
教程, Java 文件时间, Java 附加到文件,用 Java 读取文本文件,用 Java 读写 ICO 图像, Java Swing 教程,Java 教程,用 Java 显示图像。
EJB 简介
原文:http://zetcode.com/java/ejb/
在本教程中,我们学习如何使用 Enterprise JavaBeans。 我们使用 GlassFish,NetBeans,Derby 和 Maven。
Enterprise JavaBean (EJB) 是服务器端组件,封装了应用的业务逻辑。 EJB 在 EJB 容器中运行,该容器负责各种系统级服务,包括事务管理,安全性和并发控制。 EJB 是 Java EE 规范的一部分。
GlassFish 是 Java EE 的参考实现,它包括 Enterprise JavaBeans 容器。 我们将在 GlassFish 中运行示例。 Apache Derby 是完全用 Java 实现的开源关系数据库。 Oracle 以 Java DB 的名义分发相同的二进制文件。
第一个 EJB
我们在 NetBeans 中创建一个新的 Web 应用。 该项目将称为MyFirstEJB
。 从“服务器和设置”页面,选择 GlassFish 服务器,并将上下文更改为myfirstejb
。
图:服务器和设置
在此对话框中,我们选择应用服务器,Java EE 版本和上下文路径。
index.html
<!DOCTYPE html>
<html>
<head>
<title>Test page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Test page</p>
</body>
</html>
这是我们的index.html
页面。 如果我们访问应用的根页面,它将返回。
我们右键单击应用图标,然后选择一个 Session Bean 类型的新 EJB。 我们将 bean 称为MyFirstBean
,键入com.zetcode.ejb
包,然后选择无状态会话类型。
图:在 NetBeans 中创建一个新的会话 Bean
无状态会话 Bean 不维护与客户端的对话状态。 当客户端调用无状态 Bean 的方法时,该 Bean 的实例变量可能包含特定于该客户端的状态,但仅在调用期间包含。 该方法完成后,客户端特定的状态将丢失。
FirstBean.java
package com.zetcode.ejb;
import javax.ejb.Stateless;
@Stateless
public class FirstBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String sayHello() {
StringBuilder sb = new StringBuilder("Hello ");
sb.append(this.getName()).append("!");
return sb.toString();
}
}
MyFirstBean
是无状态会话 Bean。 使用@Stateless
装饰创建一个无状态会话 bean。 它具有no-interface view
,其中不使用本地业务接口,并且 Bean 类的所有公共方法都自动向调用者公开。
public String sayHello() {
StringBuilder sb = new StringBuilder("Hello ");
sb.append(this.getName()).append("!");
return sb.toString();
}
MyFirstBean's
的工作是构造对调用者的问候。
接下来,我们通过右键单击项目图标并从 Web 类别中选择 Servlet 文件类型来创建一个新的 Servlet。 我们将 servlet 称为Greet
,然后键入com.zetcode.web
包。 我们将 URL 模式更改为/greet
。
图:创建新的 servlet
在新的 Servlet 对话框中,我们提供 Servlet 名称及其包。
Greet.java
package com.zetcode.web;
import com.zetcode.ejb.FirstBean;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "Greet", urlPatterns = {"/greet"})
public class Greet extends HttpServlet {
@EJB
private FirstBean firstBean;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
firstBean.setName(request.getParameter("name"));
String msg = firstBean.createMessage();
try (PrintWriter out = response.getWriter()) {
out.println(msg);
}
}
}
Greet
Servlet 从客户端发送的 URL 中读取名称参数,调用 EJB 的createMessage()
业务方法,并以纯文本形式返回响应。
@EJB
private FirstBean firstBean;
@EJB
注解将 EJB 注入 Servlet。
response.setContentType("text/plain;charset=UTF-8");
servelt 响应以 UTF-8 字符集的纯文本格式显示。
firstBean.setName(request.getParameter("name"));
我们从请求中检索name
参数,并将其设置为 EJB。
String msg = firstBean.createMessage();
我们将其称为createMessage()
业务方法。
图:MyFirstEJB 项目结构
我们构建应用并将其部署到 GlassFish 服务器。 要构建应用,请右键单击项目图标,然后选择“构建”。 要部署应用,请右键单击项目图标,然后选择“部署”。
$ curl localhost:8080/myfirstejb/greet?name=Jan
Hello Jan!
使用curl
工具,我们连接到myfirstejb
Web 应用的Greet
servlet,并向其传递参数。 Web 应用以问候语响应。
$ curl localhost:8080/myfirstejb/
<!DOCTYPE html>
<html>
<head>
<title>Test page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Test page</p>
</body>
</html>
访问根页面,应用将返回 HTML 测试页面。
使用实体 bean 持久化数据
在第二个示例中,我们创建一个将读取和保存汽车的 Web 应用。 汽车对象将保存在 Derby 数据库中。
我们使用car-app
名称创建一个新的 Java Web 应用。 然后,我们创建一个新的Car
实体。 实体类文件类型位于持久性类别中。 包将为com.zetcode.persistence
。 主键类型为Long
,并选中了创建持久性单元选项。
在下一页中,我们将持久性单元名称更改为carpu
,然后选择默认的EclipseLink
持久性供应器。 我们选择jdbc/sample
数据源,并选择了Create
表生成策略。 jdbc/sample
数据源是指默认情况下位于用户主目录的.netbeans-derby
子目录中的sample
数据库。
图:持久性供应器
在此视图中,我们提供了持久性单元名称,持久性供应器,数据源和表生成策略。
实体 Bean
实体 Bean 是一种 Enterprise JavaBean,它表示持久存储中存在的业务实体对象。 实体 bean 由主键标识。 与在客户端会话的生存期内生存的会话 Bean 不同,实体 Bean 即使在 EJB 容器崩溃时也可以幸免。
Car.java
package com.zetcode.persistence;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="Cars")
public class Car implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="Id")
private Long id;
@Column(name="Name")
private String name;
@Column(name="Price")
private int price;
public Car() { }
public Car(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Car
是 EJB 实体 Bean。 它是要存储在 Derby 数据库中的业务对象。
@Entity
@Table(name="Cars")
public class Car implements Serializable {
实体 bean 用@Entity
注解修饰。 该实体映射到Cars
表。
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name="Id")
private Long id;
每个实体都有一个唯一的对象标识符。 此唯一标识符或主键使客户端能够定位特定的实体实例。 @Id
声明此实体 bean 的标识符属性,@GeneratedValue
注解用于指定主键的生成方式,@Column
将标识符映射到数据库表的Id
列。
public Car() { }
持久性框架需要无参数的构造器。
EJB
我们创建一个本地无状态ManageCarBean
企业 bean。 这次 EJB 具有本地接口视图。
ManageCarBeanLocal.java
package com.zetcode.ejb;
import javax.ejb.Local;
import com.zetcode.persistence.Car;
@Local
public interface ManageCarBeanLocal {
void saveCar(Car car);
void setPrice(int price);
void setName(String name);
Car getCar(Long id);
}
该接口定义了 EJB 客户端要使用的方法。 客户端只能通过 bean 的业务接口中定义的方法来访问会话 bean。
@Local
public interface ManageCarBeanLocal {
@Local
装饰指定该接口是本地业务接口。
ManageCarBean.java
package com.zetcode.ejb;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.zetcode.persistence.Car;
@Stateless
public class ManageCarBean implements ManageCarBeanLocal {
private String name;
private int price;
@PersistenceContext(unitName = "carpu")
private EntityManager em;
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
@Override
public void setPrice(int price) {
this.price = price;
}
@Override
public void saveCar(Car car) {
em.persist(car);
}
@Override
public Car getCar(Long id) {
Car car = em.find(Car.class, id);
return car;
}
}
ManageCarBean
读取并保存汽车对象。
@PersistenceContext(unitName = "carpu")
private EntityManager em;
容器使用persistence.xml
中的信息创建EntityManager
。 @PersistenceContext
注解将实体管理器注入到 Bean 中。 管理器已映射到carpu
持久性单元。 实体管理器用于通过持久性上下文与数据进行交互。
@Override
public void saveCar(Car car) {
em.persist(car);
}
saveCar()
方法将汽车对象保存到数据库中; 在我们的例子中是 Derby。
@Override
public Car getCar(Long id) {
Car car = em.find(Car.class, id);
return car;
}
getCar()
方法通过其 ID 查找汽车。
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="carpu" transaction-type="JTA">
<jta-data-source>jdbc/sample</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables" />
</properties>
</persistence-unit>
</persistence>
persistence.xml
文件是一个配置文件,其中包含有关我们在应用中使用的数据库的信息。 jdbc/sample
是 GlassFish 服务器随附的内置数据源。 在我们的应用中,我们将数据保存在 Derby 的预先创建的sample
数据库中。 如果eclipselink.ddl-generation
属性不存在,则会自动导致创建Cars
表。
图:NetBeans Derby 工具
我们可以使用 NetBeans Derby 工具来连接和管理数据库中的数据。 该工具位于“服务”窗口中。
servlet
我们创建两个 servlet:SaveCar
和ReadCar
。 我们将它们放入com.zetcode.web
包中。 Servlet 是从 NetBeans Web 类别创建的。 两个 servlet 均以纯文本形式响应。
图:Apache Common Lang JAR
我们还包括用于帮助程序方法的 Apache Common Lang JAR。 可以从项目网站下载该库。
ValidateParameter.java
package com.zetcode.util;
import org.apache.commons.lang3.math.NumberUtils;
public class ValidateParameter {
private static final int MAX_PRICE_CAR = 10_000_000;
public static boolean validateName(String param) {
return !(null == param || "".equals(param) ||
NumberUtils.isNumber(param));
}
public static boolean validateId(String param) {
return !(null == param || "".equals(param) ||
!NumberUtils.isNumber(param));
}
public static boolean validatePrice(String param) {
if (null == param || "".equals(param) || !NumberUtils.isNumber(param)) {
return false;
}
int price = Integer.valueOf(param);
return !(price < 0 || price > MAX_PRICE_CAR);
}
}
ValidateParameter
类用于验证请求参数。 参数不能为null
或为空,并且 ID 和价格值必须为数字。 另外,价格必须在合理范围内。 我们使用 Apache Common Lang 的NumberUtils.isNumber()
检查数字值。
SaveCar.java
package com.zetcode.web;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.zetcode.ejb.ManageCarBeanLocal;
import com.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;
@WebServlet(name = "SaveCar", urlPatterns = {"/save"})
public class SaveCar extends HttpServlet {
@EJB
private ManageCarBeanLocal manageCarBean;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String sname = request.getParameter("name");
String sprice = request.getParameter("price");
String message;
if (ValidateParameter.validateName(sname)
&& ValidateParameter.validatePrice(sprice)) {
message = String.format("%s car is saved", sname);
int price = Integer.valueOf(sprice);
Car car = new Car(sname, price);
manageCarBean.saveCar(car);
} else {
message = "Wrong parameters";
}
response.setContentType("text/plain;charset=UTF-8");
try (PrintWriter out = response.getWriter()) {
out.println(message);
}
}
}
SaveCar
Servlet 的目的是调用适当的 EJB 来保存汽车。 汽车对象的数据是从客户端发送的 URL 中读取的。
@WebServlet(name = "SaveCar", urlPatterns = {"/save"})
@WebServlet
注解将SaveCar
Servlet 映射到/save
路径。
@EJB
private ManageCarBeanLocal manageCarBean;
ManageCarBeanLocal
企业 bean 被注入到 servlet 中。
String sname = request.getParameter("name");
String sprice = request.getParameter("price");
从请求中读取参数。
if (ValidateParameter.validateName(sname)
&& ValidateParameter.validatePrice(sprice)) {
参数正在验证中。
Car car = new Car(sname, price);
manageCarBean.saveCar(car);
创建一个新的汽车对象并将其保存到数据库。 为了保存汽车对象,我们利用了ManageCarBean
的saveCar()
方法。
ReadCar.java
package com.zetcode.web;
import com.zetcode.ejb.ManageCarBeanLocal;
import java.io.IOException;
import java.io.PrintWriter;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.zetcode.persistence.Car;
import com.zetcode.util.ValidateParameter;
@WebServlet(name = "ReadCar", urlPatterns = {"/read"})
public class ReadCar extends HttpServlet {
@EJB
private ManageCarBeanLocal manageCarBean;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/plain;charset=UTF-8");
String message;
String pid = request.getParameter("id");
if (ValidateParameter.validateId(pid)) {
Long carId = Long.valueOf(pid);
Car rcar = manageCarBean.getCar(carId);
if (null != rcar) {
message = String.format("Name: %s, Price: %d",
rcar.getName(), rcar.getPrice());
} else {
message = "Cannot find car with this ID";
}
} else {
message = "Wrong parameters";
}
try (PrintWriter out = response.getWriter()) {
out.println(message);
}
}
}
ReadCar
servlet 调用ManageCarBean
企业 bean 从数据库中读取数据; 它根据提供的 ID 选择汽车对象。
String sid = request.getParameter("id");
从 URL 读取 ID。
Long carId = Long.valueOf(pid);
Car rcar = manageCarBean.getCar(carId);
if (null != rcar) {
message = String.format("Name: %s, Price: %d",
rcar.getName(), rcar.getPrice());
} else {
message = "Cannot find car with this ID";
}
ManageCarBean
的readCar()
方法用于检索汽车对象。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<error-page>
<error-code>404</error-code>
<location>/error404.txt</location>
</error-page>
<error-page>
<location>/error.txt</location>
</error-page>
</web-app>
在web.xml
文件中,我们提供了两个错误页面。 error.txt
是所有错误的默认错误页面,error404.txt
是 404 错误的错误页面,当客户端请求不存在的资源时触发。 注意,error404.txt
必须先于error.txt
。
图:错误页面s
我们将两个文本文件放置在网页中。
error.txt
Error has occurred, check the GlassFish log file
这是一般错误页面。
error404.txt
404 : Page was not found
这是 404 错误的错误页面。
部署方式
是时候部署应用了。 通过右键单击 Web 项目并选择 Deploy 命令来部署应用。 请注意,当我们清理项目时,将取消部署该应用。 如果未运行所选的应用服务器,那么 deploy 命令还将启动它。
$ ./asadmin list-applications
MyFirstEJB <ejb, web>
car-app <ejb, web>
Command list-applications executed successfully.
GlassFish 的asadmin
工具可用于确定当前已部署的应用。
GlassFish 启动时,NetBeans 会自动启动 Derby 服务器。 可以在 GlassFish 服务器设置中将其关闭。
图:GlassFish 服务器属性
在 NetBeans 的“服务”选项卡中,我们展开“服务器”节点,然后选择 GlassFish 属性。 在那里,我们可以看到“启动已注册 Derby 服务器”选项。
$ curl localhost:8080/car-app/doread
404 : Page was not found
如果尝试访问不存在的资源,则会收到自定义 404 错误消息。
$ curl "localhost:8080/car-app/save?name=Volvo&price=29000"
Volvo car is saved
沃尔沃汽车将保存到数据库中。 注意 URL 的双引号的用法。
$ curl "localhost:8080/car-app/save?name=Bentley&price=299000000"
Wrong parameters
验证器发现了不切实际的高汽车价格。
$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000
我们从数据库中读取了一辆汽车。
使用 Maven 构建项目
NetBeans 在开发过程中为我们节省了许多繁琐的工作。 但是使用 Maven 手动构建项目是有益的。
mvn archetype:generate -DgroupId=com.zetcode -DartifactId=car-app
-DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
我们创建一个新的 Maven 项目。 我们使用maven-archetype-webapp
类型。
$ tree
.
└── car-app
├── pom.xml
└── src
└── main
├── resources
└── webapp
├── index.jsp
└── WEB-INF
└── web.xml
6 directories, 3 files
tree
命令向我们显示了创建的项目结构。
$ mkdir -p src/main/java/com/zetcode/ejb
$ mkdir src/main/java/com/zetcode/persistence
$ mkdir src/main/java/com/zetcode/web
$ mkdir src/main/java/com/zetcode/util
$ mkdir src/main/resources/META-INF
我们创建目录。
复制应用源文件后,项目结构如下所示:
$ tree
.
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── zetcode
│ ├── ejb
│ │ ├── ManageCarBean.java
│ │ └── ManageCarBeanLocal.java
│ ├── persistence
│ │ └── Car.java
│ ├── util
│ │ └── ValidateParameter.java
│ └── web
│ ├── ReadCar.java
│ └── SaveCar.java
├── resources
│ └── META-INF
│ └── persistence.xml
└── webapp
├── index.jsp
└── WEB-INF
└── web.xml
不要忘记复制web.xml
文件。 Maven 为我们创建了一个空的web.xml
文件。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>car-app</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>car-app Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<finalName>car-app</finalName>
</build>
</project>
pom.xml
是一个文件,其中包含有关项目的信息以及 Maven 用于构建项目的配置详细信息。 我们提供了 Eclipse 链接持久性供应器,Apache Commons lang 库和 Java EE Web API 的依赖项。 我们指示 Maven 使用 Java8。最终的构建文件称为car-app.war
,位于target
子目录中。
$ mvn package
该项目是使用mvn package
命令构建的。
$ ./asadmin start-domain
从 GlassFish 的bin
目录中,启动 GlassFish 服务器。
$ ./asadmin start-database --db-home ~/.netbeans-derby
Derby 服务器已启动。 NetBeans 在.netbeans-derby
目录中创建 Derby 系统主目录,该目录位于用户的主目录中。 在这里,我们可以找到我们之前使用过的sample
数据库。
$ ~/bin/glassfish-4.1.1/glassfish/bin/asadmin deploy target/car-app.war
我们部署应用。 我们看到警告语Table/View 'CARS' already exists in Schema 'APP'
。 这是eclipselink.ddl-generation
属性的结果,该属性设置为create_tables
。 我们可以忽略警告。
$ curl localhost:8080/car-app/read?id=401
Name: Volvo, Price: 29000
我们检索先前创建的汽车。
在本教程中,我们为一些简单的业务逻辑创建了 Enterprise JavaBeans。 我们已使用 NetBeans,Derby 和 Maven 作为示例。 ZetCode 具有以下相关教程: Derby 教程, Java 教程和 Stripes 教程。
Java forEach
教程
原文:http://zetcode.com/java/foreach/
Java forEach
教程显示了如何使用 Java8 forEach()
方法。 我们与消费者合作,并在列表,映射和集合集合上展示forEach()
。
forEach()
方法是 Java8 中引入的。它为程序员提供了一种新的,简洁的迭代集合的方法。
forEach()
方法对Iterable
的每个元素执行给定的操作,直到所有元素都已处理或该操作引发异常。
void forEach(Consumer<? super T> action);
这是forEach()
方法的语法。
Consumer
接口
Consumer
接口是一个函数式接口(具有单个抽象方法的接口),它接受单个输入并且不返回结果。
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
这是Consumer
接口的定义。
com/zetcode/JavaForEachListConsumer.java
package com.zetcode;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class JavaForEachListConsumer {
public static void main(String[] args) {
List<String> items = new ArrayList<>();
items.add("coins");
items.add("pens");
items.add("keys");
items.add("sheets");
items.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
System.out.println(name);
}
});
}
}
在此示例中,我们使用forEach()
遍历字符串列表。 Java lambda 表达式可以缩短此语法。
Lambda 表达式
Lambda 表达式主要用于定义函数式接口的内联实现,即仅具有单个方法的接口。 Lambda 表达式是使用->
lambda 运算符创建的。
com/zetcode/JavaForEachListLambda.java
package com.zetcode;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
public class JavaForEachListLambda {
public static void main(String[] args) {
List<String> items = new ArrayList<>();
items.add("coins");
items.add("pens");
items.add("keys");
items.add("sheets");
items.forEach((String name) -> {
System.out.println(name);
});
}
}
这里我们有同样的例子。 lambda 表达式使示例更简洁。
Java 映射上的forEach
以下示例在映射上使用forEach()
。
com/zetcode/JavaForEachMap.java
package com.zetcode;
import java.util.HashMap;
import java.util.Map;
public class JavaForEachMap {
public static void main(String[] args) {
Map<String, Integer> items = new HashMap<>();
items.put("coins", 3);
items.put("pens", 2);
items.put("keys", 1);
items.put("sheets", 12);
items.forEach((k, v) -> {
System.out.printf("%s : %d%n", k, v);
});
}
}
我们有一个字符串/整数对的映射。 使用forEach()
方法,我们遍历映射并打印其键/值对。
在下一个示例中,我们在代码中显式显示Consumer
和Map.Entry
。
com/zetcode/JavaForEachMap2.java
package com.zetcode;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
public class ForEachMap2 {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<>();
map.put("cups", 6);
map.put("clocks", 2);
map.put("pens", 12);
Consumer<Map.Entry<String, Integer>> action = entry ->
{
System.out.printf("key: %s", entry.getKey());
System.out.printf(" value: %s%n", entry.getValue());
};
map.entrySet().forEach(action);
}
}
该示例在一个条目集上循环,该条目集是通过entrySet()
检索的。
Java 集合上的forEach
以下示例在一个集合上使用forEach()
。
com/zetcode/JavaForEachSet.java
package com.zetcode;
import java.util.HashSet;
import java.util.Set;
public class JavaForEachSet {
public static void main(String[] args) {
Set<String> brands = new HashSet<>();
brands.add("Nike");
brands.add("IBM");
brands.add("Google");
brands.add("Apple");
brands.forEach((e) -> { System.out.println(e); });
}
}
我们有一组字符串。 使用forEach()
方法,我们遍历集合并打印其值。
在数组上使用 forEach
以下示例在数组上使用forEach()
。
com/zetcode/JavaForEachArray.java
package com.zetcode;
import java.util.Arrays;
public class JavaForEachArray {
public static void main(String[] args) {
int[] nums = { 3, 4, 2, 1, 6, 7 };
Arrays.stream(nums).forEach((e) -> { System.out.println(e); });
}
}
在示例中,我们有一个整数数组。 我们使用Arrays.stream()
方法将数组转换为流。 然后forEach()
方法遍历元素并将它们打印到控制台。
过滤列表
在使用forEach()
遍历数据之前,我们可以轻松过滤数据。
com/zetcode/JavaForEachListFilter.java
package com.zetcode;
import java.util.ArrayList;
import java.util.List;
public class JavaForEachListFilter {
public static void main(String[] args) {
List<String> items = new ArrayList<>();
items.add("coins");
items.add("pens");
items.add("keys");
items.add("sheets");
items.stream().filter(item -> (item.length() == 4)).forEach(System.out::println);
}
}
在此示例中,我们过滤字符串列表,并将过滤后的列表打印到控制台。 仅显示具有四个字符的字符串。
IntConsumer
,LongConsumer
,DoubleConsumer
从 Java8 开始,我们为原始数据类型内置了使用者接口:IntConsumer
,LongConsumer
和DoubleConsumer
。
com/zetcode/JavaForEachConsSpec.java
package com.zetcode;
import java.util.Arrays;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;
public class JavaForEachConsSpec {
public static void main(String[] args) {
int[] inums = { 3, 5, 6, 7, 5 };
IntConsumer icons = i -> System.out.print(i + " ");
Arrays.stream(inums).forEach(icons);
System.out.println();
long[] lnums = { 13L, 3L, 6L, 1L, 8L };
LongConsumer lcons = l -> System.out.print(l + " ");
Arrays.stream(lnums).forEach(lcons);
System.out.println();
double[] dnums = { 3.4d, 9d, 6.8d, 10.3d, 2.3d };
DoubleConsumer dcons = d -> System.out.print(d + " ");
Arrays.stream(dnums).forEach(dcons);
System.out.println();
}
}
在示例中,我们创建了三种类型的使用者,并使用forEach()
对其进行了迭代。
在本教程中,我们介绍了 Java8 forEach()
方法。 我们已经介绍了使用者,并在列表,映射和集合上使用了forEach()
。
您可能也对以下相关教程感兴趣: Java 教程,用 Java8 的StringJoiner
连接字符串,Google Guava 简介,Java 过滤列表或 Android 教程中的代码。
列出所有 Java 教程。
Jetty 教程
原文:http://zetcode.com/java/jetty/
这是 Jetty 教程。 这是一个初学者教程,致力于进行一些基本的 Jetty 编程和管理。
目录
Jetty
Jetty 是一个 Servlet 容器和 Web 服务器。 它可以作为独立服务器运行,也可以以嵌入式模式运行。 该项目是在 Eclipse Foundation 下开发的。
相关教程
嵌入式 Tomcat 教程显示了如何在嵌入式模式下使用 Tomcat。 Java 教程描述了 Java 编程语言。 MongoDB Java 教程涵盖了 Java 中的 MongoDB 编程。
Java 基础
原文:http://zetcode.com/lang/java/basics/
在 Java 教程的这一部分中,我们涵盖了 Java 语言的一些基本编程概念。 我们从一些简单的程序开始。 我们处理变量,常量和基本数据类型。 我们在控制台上进行读写,还提到了字符串格式。
Java 简单示例
我们从一个非常简单的代码示例开始。 以下代码放置在Simple.java
文件中。 这里的命名很重要。 Java 程序的公共类必须与文件名匹配。
com/zetcode/Simple.java
package com.zetcode;
public class Simple {
public static void main(String[] args) {
System.out.println("This is Java");
}
}
从一开始就严格组织 Java 代码。 Java 代码文件可以具有一个或多个类,其中只有一个可以声明为公共。
package com.zetcode;
包用于将 Java 类组织成组,这些组通常共享相似的功能。 包类似于其他编程语言中的名称空间和模块。 对于简单的代码示例,可以省略包声明。 这将创建一个所谓的默认包。 但是,在本教程中,我们将为所有示例使用一个包。 另一个重要的事情是目录结构必须反映包名称。 在我们的情况下,带有包com.zetcode
的源文件Simple.java
必须放置在名为com/zetcode/
的目录中。 package
语句必须在源文件的第一行。
public class Simple {
...
}
类是 Java 程序的基本构建块。 通过public
关键字,可以不受限制地访问此类。 上面的代码是一个类定义。 该定义的主体以左大括号{
开头,以右大括号}
结尾。 在源文件中只能声明一个类public
。 另请注意类的名称。 其名称必须与文件名匹配。 源文件称为Simple.java
,类名为Simple
。 按照惯例,类名以大写字母开头。
public static void main(String[] args) {
...
}
main()
是一种方法。 方法是为执行特定工作而创建的一段代码。 我们没有将所有代码放在一个地方,而是将其分为称为方法的部分。 这为我们的应用带来了模块化。 每个方法都有一个放置语句的主体。 方法的主体用大括号括起来。 main()
方法的特定工作是启动应用。 它是每个控制台 Java 程序的入口点。
该方法声明为static
。 无需创建 Java 类的实例即可调用此静态方法。 首先,我们需要启动应用,然后我们可以创建类的实例。 void
关键字指出该方法未返回值。 最后,public
关键字使main()
方法不受限制地可用于外部世界。 这些主题将在后面更详细地说明。
System.out.println("This is Java");
在main()
方法中,我们发表了一条声明。 该语句将"This is Java"
(这是字符串字面值)打印到控制台。 每个语句必须以分号;
字符结尾。 该语句是一个方法调用。 我们称为System
类的println()
方法。 该类表示控制台应用的标准输入,输出和错误流。 我们指定println()
方法的标准名称。
$ java Simple.java
This is Java
我们使用java
工具执行该程序。
注意:我们使用
java
工具执行(单个)源文件。 Java 11 中添加了此功能。
Java 控制台读取值
第二个示例将显示如何从控制台读取值。
com/zetcode/ReadLine.java
package com.zetcode;
import java.util.Scanner;
public class ReadLine {
public static void main(String[] args) {
System.out.print("Write your name:");
Scanner sc = new Scanner(System.in);
String name = sc.nextLine();
System.out.println("Hello " + name);
}
}
终端窗口上会显示一个提示。 用户将其姓名写在终端上,然后读取该值并将其打印回终端。
import java.util.Scanner;
Java 标准库具有供程序员使用的大量类。 它们被组织在包中。 Scanner
类是其中之一。 当我们使用import
关键字导入一个类时,我们可以稍后在没有完整包名称的情况下引用该类。 否则,我们必须使用标准名称。 import
允许快捷引用类。 这与某些其他语言不同。 例如在 Python 中,import
关键字将对象导入脚本的名称空间。 在 Java 中,import
关键字仅通过允许引用类型而不指定全名来保存类型。
System.out.print("Write your name:");
我们向用户打印一条消息。 我们使用print()
方法不会开始新行。 然后,用户在消息旁边键入他的响应。
Scanner sc = new Scanner(System.in);
将创建Scanner
类的新实例。 使用new
关键字创建新对象。 对象的构造器使用new
关键字。 我们将一个参数添加到Scanner
对象的构造器中。 它是标准输入流。 这样我们就可以从终端上阅读了。 Scanner
是一个简单的文本扫描器,可以解析原始类型和字符串。
String name = sc.nextLine();
对象具有执行某些任务的方法。 nextLine()
方法从终端读取下一行。 它以String
数据类型返回结果。 返回的值存储在我们声明为String
类型的名称变量中。
System.out.println("Hello " + name);
我们将消息打印到终端。 该消息由两部分组成。 "Hello"
字符串和名称变量。 我们使用+
运算符将这两个值连接为一个字符串。 该运算符可以连接两个或多个字符串。
$ java ReadLine.java
Write your name:Jan Bodnar
Hello Jan Bodnar
这是第二个程序的示例执行。
Java 命令行参数
Java 程序可以接收命令行参数。 当我们运行程序时,它们会遵循程序的名称。
com/zetcode/CommandLineArgs.java
package com.zetcode;
public class CommandLineArgs {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
命令行参数可以传递给main()
方法。
public static void main(String[] args)
main()
方法接收命令行参数的字符串数组。 数组是数据的集合。 数组由类型声明,后跟一对方括号[]
。 因此String[] args
构造声明了一个字符串数组。 args
是main()
方法的参数。 然后该方法可以使用传递给它的参数。
for (String arg : args) {
System.out.println(arg);
}
我们使用for
循环遍历这些参数的数组,并将它们打印到控制台。 for
循环由循环组成。 在这种情况下,循环数等于数组中的参数数。 在每个循环中,新元素将从args
数组传递到arg
变量。 传递数组的所有元素后,循环结束。 for
语句的主体由大括号{}
包围。 在此主体中,我们放置了要在每个循环中执行的语句。 在我们的例子中,我们仅将arg
变量的值打印到终端。 循环和数组将在后面更详细地描述。
$ java CommandLineArgs.java 1 2 3 4 5
1
2
3
4
5
我们提供四个数字作为命令行参数,这些数字将打印到控制台。
当从命令行启动程序时,我们在程序名称后立即指定参数。 在诸如 IntelliJ IDEA 的集成开发环境(IDE)中,我们在对话框中指定这些参数。 在 IntelliJ IDEA 中,我们选择“编辑配置”,然后将值添加到“程序参数”选项中。
Java 变量
变量是存储数据的地方。 变量具有名称和数据类型。 数据类型确定可以为变量分配哪些值。 整数,字符串,布尔值等。在程序运行过程中,变量可以获得相同数据类型的各种值。 在对变量进行任何引用之前,总是将 Java 中的变量初始化为其类型的默认值。
com/zetcode/Variables.java
package com.zetcode;
public class Variables {
public static void main(String[] args) {
String city = "New York";
String name = "Paul"; int age = 34;
String nationality = "American";
System.out.println(city);
System.out.println(name);
System.out.println(age);
System.out.println(nationality);
city = "London";
System.out.println(city);
}
}
在上面的示例中,我们使用四个变量。 其中三个变量是字符串。 age
变量是整数。 int
关键字用于声明整数变量。
String city = "New York";
我们声明一个字符串类型的city
变量,并将其初始化为"New York"
值。
String name = "Paul"; int age = 34;
我们声明并初始化两个变量。 我们可以将两个语句放在一行中。 由于每个语句都以分号结尾,因此 Java 编译器知道一行中有两个语句。 但是出于可读性原因,每个语句应放在单独的行上。
System.out.println(city);
System.out.println(name);
System.out.println(age);
System.out.println(nationality);
我们将变量的值打印到终端。
city = "London";
System.out.println(city);
我们为城市变量分配一个新值,然后打印。
$ java Variables.java
New York
Paul
34
American
London
这是示例的输出。
var
关键字
由于 Java 10 用于带有初始化器的局部变量,因此我们可以使用var
关键字代替数据类型。 数据类型将从声明的右侧推断出来。
com/zetcode/VarKeyword.java
package com.zetcode;
public class VarKeyword {
public static void main(String[] args) {
var name = "John Doe";
var age = 34;
System.out.println(name + " is " + age + " years old");
}
}
在示例中,我们将var
关键字用于两个变量。
var name = "John Doe";
var age = 34;
我们有一个字符串变量和一个整数变量。 编译器从声明的右侧推断出数据类型。 为了使推断起作用,必须初始化变量。
Java 常量
与变量不同,常量不能更改其初始值。 一旦初始化,便无法修改。 使用final
关键字创建常量。
com/zetcode/Constants.java
package com.zetcode;
public class Constants {
public static void main(String[] args) {
final int WIDTH = 100;
final int HEIGHT = 150;
int var = 40;
var = 50;
//WIDTH = 110;
}
}
在此示例中,我们声明两个常量和一个变量。
final int WIDTH = 100;
final int HEIGHT = 150;
我们使用final
关键字通知编译器我们声明了一个常量。 按照惯例,用大写字母写常量。
int var = 40;
var = 50;
我们声明并初始化一个变量。 稍后,我们为变量分配一个新值。 是合法的
// WIDTH = 110;
无法为常数分配新值。 如果我们取消注释此行,则会出现编译错误:“不可编译的源代码-无法将值分配给最终变量WIDTH
”。
Java 字符串格式
从变量构建字符串是编程中非常常见的任务。 Java 语言具有String.format()
方法来格式化字符串。
一些动态语言(如 Perl,PHP 或 Ruby)支持变量插值。 变量插值正在用字符串字面值中的值替换变量。 Java 语言不允许这样做。 它具有字符串格式。
com/zetcode/StringFormatting.java
package com.zetcode;
public class StringFormatting {
public static void main(String[] args) {
int age = 34;
String name = "William";
String output = String.format("%s is %d years old.", name, age);
System.out.println(output);
}
}
在 Java 中,字符串是不可变的。 我们无法修改现有字符串。 我们必须从现有字符串和其他类型创建一个新字符串。 在代码示例中,我们创建一个新字符串。 我们还使用来自两个变量的值。
int age = 34;
String name = "William";
这里我们有两个变量,一个整数和一个字符串。
String output = String.format("%s is %d years old.", name, age);
我们使用内置String
类的format()
方法。 %s
和%d
是控制字符,稍后进行求值。 %s
接受字符串值,%d
整数值。
$ java StringFormatting.java
William is 34 years old.
This is the output of the example.
本章介绍了 Java 语言的一些基础知识。
Tomcat Derby 教程
原文:http://zetcode.com/java/tomcatderby/
在本教程中,我们将使用 Tomcat 和 Derby。 该应用分为四个层,它具有一个控制器,并使用 DAO 访问数据。 对于项目创建,我们使用 NetBeans IDE。 源代码可从作者的 Github TomcatDerby 仓库中获得。
现代 Java Web 应用主要通过使用诸如 Spring 或 Vaadin 之类的框架来创建。 但是有必要了解基础。
我们使用pure.css
库来帮助创建用户界面。
Apache Derby 是完全用 Java 实现的开源关系数据库。 它占地面积小,易于部署和安装。 它支持嵌入式和客户端/服务器模式。
Apache Tomcat 是 Java Servlet,JavaServer Pages,Java Expression Language 和 Java WebSocket 技术的开源实现。
数据访问对象(DAO) 是为数据库或其他持久性机制提供抽象接口的对象。 DAO 完全向其客户端隐藏了数据源实现细节。 它充当组件和数据源之间的适配器。
Java Web 应用
我们在 NetBeans 中创建一个新的 Web 应用。 该应用管理一个简单的Cars
表。 它将创建新的汽车,检索一辆汽车和所有汽车。
该应用分为四个层:表示层,模型层,服务层和持久层。 Web 应用的多层设置是重要的软件开发模式。
$ tree
.
├── nb-configuration.xml
├── pom.xml
├── README.md
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── zetcode
│ │ ├── bean
│ │ │ └── Car.java
│ │ ├── persistence
│ │ │ ├── CarDAO.java
│ │ │ ├── Executable.java
│ │ │ └── JdbcDAO.java
│ │ ├── service
│ │ │ ├── CarsService.java
│ │ │ └── ICarsService.java
│ │ ├── util
│ │ │ ├── DBUtils.java
│ │ │ ├── ServiceLocator.java
│ │ │ └── ValidateParameter.java
│ │ └── web
│ │ └── Controller.java
│ └── webapp
│ ├── allCars.jsp
│ ├── carSaved.jsp
│ ├── index.jsp
│ ├── META-INF
│ │ └── context.xml
│ ├── readCarId.jsp
│ ├── readCar.jsp
│ ├── showCar.jsp
│ ├── unknown.jsp
│ ├── WEB-INF
│ └── wrongParams.jsp
└── test
└── java
这是项目结构。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mycompany</groupId>
<artifactId>DerbyTomcat</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>DerbyTomcat</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derbyclient</artifactId>
<version>10.14.1.0</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom.xml
包含项目依赖项:servlet 的 JAR,JSP 页面,Derby 数据库驱动程序,JSTL 库以及某些帮助程序类的 Apache Commons Lang JAR。
图:数据库创建
在“服务”选项卡中,我们右键单击 Java DB 节点,然后选择“创建数据库”选项。 我们给它命名为testdb
。 该数据库位于用户主目录的.netbeans_derby
目录中。
cars.sql
CREATE TABLE CARS(ID BIGINT NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY
(START WITH 1, INCREMENT BY 1), NAME VARCHAR(30), PRICE INT);
INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);
这是创建Cars
表的 SQL。 汽车对象的 ID 会自动增加。 我们可以使用 NetBeans 工具创建Cars
表。 我们右键单击“数据库”节点,然后选择“新建连接”选项。
图:连接向导
我们在连接向导中填写必要的详细信息。 我们使用 Derby 网络驱动程序; Derby 的端口是 1527。
图:连接
创建一个新的连接对象; 它由橙色图标表示。 其上下文菜单提供了用于连接到指定数据库并执行命令的选项。 “执行命令”选项显示了执行 SQL 命令的工具。 在此窗口中,我们可以使用上面的 SQL 创建Cars
表。
图:NetBeans Derby 工具
NetBeans 具有有用的 Derby 工具,可用于管理 Derby 数据库。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/DerbyTomcat">
<Resource name="jdbc/testdb"
auth="Container"
type="javax.sql.DataSource"
driverClassName="org.apache.derby.jdbc.ClientDriver"
username="app"
password="app"
url="jdbc:derby://localhost:1527/testdb"
maxActive="10" maxIdle="4"
/>
</Context>
在META-INF
目录中的context.xml
文件中,我们提供了数据源。 使用 JNDI API 查找资源。
模型层
模型层具有Car
类。
Car.java
package com.zetcode.bean;
import java.util.Objects;
public class Car {
private Long id;
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 7;
hash = 61 * hash + Objects.hashCode(this.id);
hash = 61 * hash + Objects.hashCode(this.name);
hash = 61 * hash + this.price;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Car other = (Car) obj;
if (this.price != other.price) {
return false;
}
if (!Objects.equals(this.name, other.name)) {
return false;
}
return Objects.equals(this.id, other.id);
}
}
Car
类具有三个属性以及相应的获取器和设置器方法。
表示层
应用的表示层包含 JSP 页面,这些页面构建了应用的用户界面。
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Home page</h2>
<p>Available actions:</p>
<ul>
<li><a href="controller?action=listcars">Show all</a></li>
<li><a href="controller?action=readbyid">Show car by ID</a></li>
<li><a href="controller?action=readcar">Create a new car</a></li>
</ul>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
index.jsp
页面包含三个可用操作的链接:显示所有汽车,显示通过其 ID 找到的汽车以及创建新汽车。
<a href="<%= request.getContextPath() %>">Home</a>
使用getContextPath()
方法,我们可以获得主页路径。
allCars.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Cars</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<style>
body { padding:1em }
nav { margin-top: 2em }
</style>
</head>
<body>
<h2>All cars</h2>
<table class="pure-table pure-table-horizontal">
<thead>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<c:forEach items="${carList}" var='car'>
<tr>
<td>
<c:out value="${car.id}"/>
</td>
<td>
<c:out value="${car.name}"/>
</td>
<td>
<c:out value="${car.price}"/>
</td>
</tr>
</c:forEach>
</table>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
在allCars.jsp
页面中,JSTL 的<c:forEach>
和<c:out>
标签用于打印每个返回的汽车对象的属性。
<table class="pure-table pure-table-horizontal">
我们使用pure.css
类来设计表。
readCar.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true" %>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Car details</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<style>
body { padding:1em }
nav { margin-top: 2em }
</style>
</head>
<body>
<form class="pure-form pure-form-stacked" action="controller?action=savecar" method="post">
<legend>Enter car details:</legend>
<label for="carName">Name:</label>
<input id="carName" type="text" name="carName">
<label for="carPrice">Price:</label>
<input id ="carPrice" type="text" name="carPrice">
<button class="pure-button pure-button-primary" type="submit">Submit</button>
</form>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
在readCar.jsp
页面上,我们有一个表格来输入新车的详细信息。
readCarId.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Enter car ID</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css"
integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w"
crossorigin="anonymous">
<style>
body { padding:1em }
nav { margin-top:2em }
</style>
</head>
<body>
<form class="pure-form pure-form-stacked" action="controller">
<legend>Enter car Id</legend>
<input type="hidden" name="action" value="viewcar">
<label for="carId">Id:</label>
<input id="carId" type="text" name="carId">
<button class="pure-button pure-button-primary" type="submit">Submit</button>
</form>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
在readCarId.jsp
文件中,我们有一个表格来输入我们要检索的汽车 ID。
carSaved.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Car saved</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<p>
Successfully saved <c:out value="${sessionScope.carName}"/>
car priced <c:out value="${sessionScope.carPrice}"/>
</p>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
在carSaved.jsp
页面中,我们仅通知您已保存具有给定名称和价格的汽车。 我们使用 JSTL 库中的<c:out>
标签。
showCar.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<title>Returned car</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Car details</h2>
<ul>
<li>ID: <c:out value="${returnedCar.id}"/></li>
<li>Name: <c:out value="${returnedCar.name}"/></li>
<li>Price: <c:out value="${returnedCar.price}"/></li>
</ul>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
在showCar.jsp
页面中,我们显示检索到的汽车的属性。
unknown.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Unknown action</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Unknown action</h2>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
当控制器收到未定义的动作时,将显示unknown.jsp
页面。
wrongParams.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@page trimDirectiveWhitespaces="true"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html>
<html>
<head>
<title>Wrong parameters</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h2>Wrong parameters specified</h2>
<a href="<%= request.getContextPath() %>">Home</a>
</body>
</html>
请求参数无效时,显示wrongParams.jsp
页面。 创建名为ValidateParameter
的工具类以确保请求参数的有效性。
服务层
服务层提供逻辑以对发送到 DAO 和从 DAO 发送的数据进行操作。
ICarsService.java
package com.zetcode.service;
import com.zetcode.bean.Car;
import java.util.List;
public interface ICarsService {
public List<Car> findAllCars();
public Car findCar(Long id);
public void saveCar(Car car);
}
ICarsService
为汽车服务提供了三种合同方式。 我们提供了一种检索所有汽车,查找特定汽车并保存新汽车的方法。
CarsService.java
package com.zetcode.service;
import com.zetcode.bean.Car;
import com.zetcode.persistence.CarDAO;
import com.zetcode.persistence.JdbcDAO;
import java.util.List;
public class CarsService implements ICarsService {
@Override
public List<Car> findAllCars() {
CarDAO carDAO = new JdbcDAO();
return carDAO.findAll();
}
@Override
public Car findCar(Long id) {
CarDAO carDAO = new JdbcDAO();
return carDAO.findCar(id);
}
@Override
public void saveCar(Car car) {
CarDAO carDAO = new JdbcDAO();
carDAO.saveCar(car);
}
}
CarsService
提供合同方法的实现。
@Override
public List<Car> findAllCars() {
CarDAO carDAO = new JdbcDAO();
return carDAO.findAll();
}
findAllCars()
方法创建一个JdbcDAO
并调用其findAll()
方法。
持久层
在持久层中,我们应用 DAO 模式。 DAO 在定义的 API 中隐藏了数据库编程的复杂性。
CarDAO.java
package com.zetcode.persistence;
import com.zetcode.bean.Car;
import java.util.List;
public interface CarDAO {
public void saveCar(Car car);
public Car findCar(Long id);
public List<Car> findAll();
}
这是CarDAO
接口,显示用于访问我们的数据库的方法签名。
Executable.java
package com.zetcode.persistence;
import java.sql.SQLException;
import javax.naming.NamingException;
public interface Executable {
void exec() throws SQLException, NamingException;
}
Executable
接口是将try/catch/finally
样板放入exec()
方法的合约。
JdbcDAO.java
package com.zetcode.persistence;
import com.zetcode.bean.Car;
import com.zetcode.util.DBUtils;
import com.zetcode.util.ServiceLocator;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.NamingException;
import java.sql.SQLException;
import javax.sql.DataSource;
public class JdbcDAO implements CarDAO {
private static final String DATA_SOURCE = "java:comp/env/jdbc/testdb";
private Connection con;
private ResultSet rs;
private PreparedStatement pst;
@Override
public void saveCar(Car car) {
execute(() -> {
DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
con = ds.getConnection();
pst = con.prepareStatement("INSERT INTO CARS(Name, Price) VALUES(?, ?)");
pst.setString(1, car.getName());
pst.setInt(2, car.getPrice());
pst.executeUpdate();
});
}
@Override
public Car findCar(Long id) {
Car car = new Car();
execute(() -> {
DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
con = ds.getConnection();
pst = con.prepareStatement("SELECT * FROM CARS WHERE Id = (?)");
pst.setLong(1, id);
rs = pst.executeQuery();
if (rs.next()) {
car.setId(rs.getLong(1));
car.setName(rs.getString(2));
car.setPrice(rs.getInt(3));
}
});
return car;
}
@Override
public List<Car> findAll() {
List<Car> carList = new ArrayList<>();
execute(() -> {
DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
con = ds.getConnection();
pst = con.prepareStatement("SELECT * FROM CARS");
rs = pst.executeQuery();
while (rs.next()) {
Car car = new Car();
car.setId(rs.getLong(1));
car.setName(rs.getString(2));
car.setPrice(rs.getInt(3));
carList.add(car);
}
});
return carList;
}
private void execute(Executable executable) {
try {
executable.exec();
} catch (NamingException | SQLException e) {
Logger lgr = Logger.getLogger(JdbcDAO.class.getName());
lgr.log(Level.SEVERE, e.getMessage(), e);
} finally {
DBUtils.closeResultSet(rs);
DBUtils.closeStatement(pst);
DBUtils.closeConnection(con);
}
}
}
JdbcDAO
是CarDAO
接口的具体实现。 它使用 JDBC 从Cars
表中插入和检索数据。
private static final String DATA_SOURCE = "java:comp/env/jdbc/testdb";
这是用于定位testdb
数据库的 JNDI 资源名称。
@Override
public void saveCar(Car car) {
execute(() -> {
DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
con = ds.getConnection();
pst = con.prepareStatement("INSERT INTO CARS(Name, Price) VALUES(?, ?)");
pst.setString(1, car.getName());
pst.setInt(2, car.getPrice());
pst.executeUpdate();
});
}
saveCar()
方法保存一个新的汽车对象。 ServiceLocator.getDataSource()
方法查找并返回数据源。 该代码已插入execute()
方法中,该方法将处理try/catch/finally
样板。
@Override
public Car findCar(Long id) {
Car car = new Car();
execute(() -> {
DataSource ds = ServiceLocator.getDataSource(DATA_SOURCE);
con = ds.getConnection();
pst = con.prepareStatement("SELECT * FROM CARS WHERE Id = (?)");
pst.setLong(1, id);
rs = pst.executeQuery();
if (rs.next()) {
car.setId(rs.getLong(1));
car.setName(rs.getString(2));
car.setPrice(rs.getInt(3));
}
});
return car;
}
findCar()
方法从Cars
表中检索新车。 它执行准备好的语句,该语句接收汽车的 ID。 一个新的car
bean 填充了返回的数据。
private void execute(Executable executable) {
try {
executable.exec();
} catch (NamingException | SQLException e) {
Logger lgr = Logger.getLogger(JdbcDAO.class.getName());
lgr.log(Level.SEVERE, e.getMessage(), e);
} finally {
DBUtils.closeResultSet(rs);
DBUtils.closeStatement(pst);
DBUtils.closeConnection(con);
}
}
处理异常的重复代码位于execute()
方法中。
工具类
我们创建了三个工具类:ServiceLocator
,ValidateParameter
和DBUtils
。 这些类位于com.zetcode.util
包中。
ServiceLocator.java
package com.zetcode.util;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class ServiceLocator {
public static DataSource getDataSource(String jndiName) throws NamingException {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(jndiName);
return ds;
}
}
ServiceLocator
查找并返回数据源。 从JdbcDAO
类调用它。 数据源的详细信息在context.xml
文件中指定。
ValidateParameter.java
package com.zetcode.util;
import org.apache.commons.lang3.math.NumberUtils;
public class ValidateParameter {
private static final int MAX_PRICE_CAR = 10_000_000;
public static boolean validateName(String param) {
return !(null == param || "".equals(param));
}
public static boolean validateId(String param) {
return !(null == param || "".equals(param) ||
!NumberUtils.isCreatable(param));
}
public static boolean validatePrice(String param) {
if (null == param || "".equals(param) || !NumberUtils.isCreatable(param)) {
return false;
}
int price = Integer.valueOf(param);
return !(price < 0 || price > MAX_PRICE_CAR);
}
}
ValidateParameter
具有用于验证请求参数的静态方法。 例如,ID 必须为数字,且价格不得为负。 我们使用 Apache Commons Lang 库中的NumberUtils.isCreatable()
方法来确保参数为数字。
DBUtils.java
package com.zetcode.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DBUtils {
private static final Logger logger = Logger.getLogger(DBUtils.class.getName());
public static void closeResultSet(ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (SQLException ex) {
logger.log(Level.FINEST, "Could not close JDBC ResultSet", ex);
} catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.log(Level.FINEST, "Unexpected exception on closing JDBC ResultSet", ex);
}
}
}
public static void closeStatement(Statement stmt) {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
logger.log(Level.FINEST, "Could not close JDBC Statement", ex);
} catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.log(Level.FINEST, "Unexpected exception on closing JDBC Statement", ex);
}
}
}
public static void closeConnection(Connection con) {
if (con != null) {
try {
con.close();
} catch (SQLException ex) {
logger.log(Level.FINEST, "Could not close JDBC Connection", ex);
} catch (Throwable ex) {
// We don't trust the JDBC driver: It might throw RuntimeException or Error.
logger.log(Level.FINEST, "Unexpected exception on closing JDBC Connection", ex);
}
}
}
}
DBUtils
包含释放数据库资源和处理异常的方法。
控制器
Controller
是一个 Servlet,它接收传入的请求,调用服务方法并发送响应。
Controller.java
package com.zetcode.web;
import com.zetcode.bean.Car;
import com.zetcode.persistence.CarDAO;
import com.zetcode.persistence.JdbcDAO;
import com.zetcode.service.CarsService;
import com.zetcode.service.ICarsService;
import com.zetcode.util.ValidateParameter;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "Controller", urlPatterns = {"/controller"})
public class Controller extends HttpServlet {
private static final String ACTION_KEY = "action";
private static final String READ_CAR_BY_ID_VIEW = "readCarId.jsp";
private static final String SHOW_CAR_VIEW = "showCar.jsp";
private static final String READ_CAR_VIEW = "readCar.jsp";
private static final String CAR_SAVED_VIEW = "carSaved.jsp";
private static final String ALL_CARS_VIEW = "allCars.jsp";
private static final String UNKNOWN_VIEW = "unknown.jsp";
private static final String WRONG_PARAMS_VIEW = "wrongParams.jsp";
private static final String LIST_CARS_ACTION = "listcars";
private static final String READ_CAR_BY_ID_ACTION = "readbyid";
private static final String READ_CAR_ACTION = "readcar";
private static final String VIEW_CAR_ACTION = "viewcar";
private static final String SAVE_CAR_ACTION = "savecar";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String actionName = request.getParameter(ACTION_KEY);
String page = UNKNOWN_VIEW;
if (LIST_CARS_ACTION.equals(actionName)) {
ICarsService service = new CarsService();
request.setAttribute("carList", service.findAllCars());
page = ALL_CARS_VIEW;
}
if (READ_CAR_BY_ID_ACTION.equals(actionName)) {
page = READ_CAR_BY_ID_VIEW;
}
if (READ_CAR_ACTION.equals(actionName)) {
page = READ_CAR_VIEW;
}
if (VIEW_CAR_ACTION.equals(actionName)) {
String sid = request.getParameter("carId");
if (ValidateParameter.validateId(sid)) {
ICarsService service = new CarsService();
Long carId = Long.valueOf(sid);
request.setAttribute("returnedCar", service.findCar(carId));
page = SHOW_CAR_VIEW;
} else {
page = WRONG_PARAMS_VIEW;
}
}
RequestDispatcher disp = getServletContext().getRequestDispatcher("/" + page);
disp.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String actionName = request.getParameter(ACTION_KEY);
String page = UNKNOWN_VIEW;
if (SAVE_CAR_ACTION.equals(actionName)) {
String sname = request.getParameter("carName");
String sprice = request.getParameter("carPrice");
if (ValidateParameter.validateName(sname)
&& ValidateParameter.validatePrice(sprice)) {
Car car = new Car();
car.setName(sname);
car.setPrice(Integer.valueOf(sprice));
ICarsService service = new CarsService();
service.saveCar(car);
request.getSession().setAttribute("carName", sname);
request.getSession().setAttribute("carPrice", sprice);
page = CAR_SAVED_VIEW;
} else {
page = WRONG_PARAMS_VIEW;
}
}
response.sendRedirect(page);
}
}
该 servlet 位于com.zetcode.web
包中。
private static final String READ_CAR_BY_ID_VIEW = "readCarId.jsp";
private static final String SHOW_CAR_VIEW = "showCar.jsp";
private static final String READ_CAR_VIEW = "readCar.jsp";
...
这些是我们的应用中使用的各种视图。
private static final String LIST_CARS_ACTION = "listcars";
private static final String READ_CAR_BY_ID_ACTION = "readbyid";
private static final String READ_CAR_ACTION = "readcar";
...
这些是应用的各种动作。 例如,READ_CAR_ACTION
显示的视图包含一个表单,用户可以在其中输入新车的详细信息。
if (LIST_CARS_ACTION.equals(actionName)) {
ICarsService service = new CarsService();
request.setAttribute("carList", service.findAllCars());
page = ALL_CARS_VIEW;
}
对于LIST_CARS_ACTION
,我们创建一个CarsService
对象。 我们调用findAllCars()
服务方法,并将结果设置为carList
属性。 然后,控制器 Servlet 指向ALL_CARS_VIEW
。
if (VIEW_CAR_ACTION.equals(actionName)) {
String sid = request.getParameter("carId");
if (ValidateParameter.validateId(sid)) {
ICarsService service = new CarsService();
Long carId = Long.valueOf(sid);
request.setAttribute("returnedCar", service.findCar(carId));
page = SHOW_CAR_VIEW;
} else {
page = WRONG_PARAMS_VIEW;
}
}
为了查看一辆汽车,我们从request
参数中获得了汽车的 ID。 该值使用ValidateParameter.validateId()
工具方法进行验证。 (该值不能为null
,为空,并且必须为数字。)如果参数无效,则控制器导航至WRONG_PARAMS_VIEW
。
findCar()
尝试从数据库中检索汽车。 返回的汽车将插入returnedCar
属性,该属性随后会在showCar.jsp
页面中获取。
if (ValidateParameter.validateName(sname)
&& ValidateParameter.validatePrice(sprice)) {
Car car = new Car();
car.setName(sname);
car.setPrice(Integer.valueOf(sprice));
ICarsService service = new CarsService();
service.saveCar(car);
request.getSession().setAttribute("carName", sname);
request.getSession().setAttribute("carPrice", sprice);
page = CAR_SAVED_VIEW;
} else {
page = WRONG_PARAMS_VIEW;
}
该代码位于doPost()
方法中。 保存新车时,我们有两个参数:汽车的名称和价格。 该 ID 由 Derby 自动创建。 验证参数,并创建一个新的Car
对象并填充参数。 saveCar()
将汽车对象保存到数据库中。 汽车的名称将传递到CAR_SAVED_VIEW
,以便向用户创建消息。 由于我们在doPost()
方法中使用了重定向,因此我们将汽车的名称及其价格放入会话对象; 进行重定向操作后,我们会从原始请求中删除数据。
response.sendRedirect(page);
遵循发布/重定向/获取模式,我们将重定向到doPost()
方法中的视图。 这样可以防止提交多个表单。 (例如,我们可能不小心多次添加了汽车)。
图:显示所有汽车
在本教程中,我们创建了一个简单的 Web 应用框架,用于管理汽车对象。 数据已保存在 Derby 数据库中。
该应用分为四层。 我们已经使用 DAO 模式进行数据访问。 您可以在 ZetCode 的 Derby 教程中找到有关 Derby 的更多信息。 在中显示数据网格中的数据教程中,我们展示了如何在 EasyUI datagrid 控件中显示来自 Derby 数据库的数据。
Stripes 介绍
原文:http://zetcode.com/java/stripes/
这是 Stripes 入门教程。 我们使用 Stripes Web 框架创建两个简单的 Web 应用。 我们使用 NetBeans 来构建应用。
Stripes 是一个开源的轻量级 Java Web 应用框架。 Stripes 的目标是使 Java 中基于 Servlet/JSP 的 Web 开发变得简单,直观和直接。 Stripes 是基于动作的 MVC(模型视图控制器)框架。 它运行在 JEE Web 容器中,使用最少的配置文件,并具有灵活和简单的参数绑定。
Stripes 的ActionBean
是一个对象,用于接收在请求中提交的数据并处理用户的输入。 它既定义了表单的属性,又定义了表单的处理逻辑。 Stripes 会在部署时通过扫描 Web 应用的类路径来自动发现ActionBean
。 条带过滤器的ActionResolver.Packages
init-param
(在web.xml
中)设置一个或多个包根。
分辨率是作为对已处理请求的响应而创建的对象。 解决方案可以转发到 JSP 页面,流数据或返回错误消息。 分辨率由ActionBeans
的处理器方法返回。
从 Stripes 的 Github 页面中,我们下载了最新的 Stripes 版本。 在lib
子目录中,我们需要在项目中包含三个 JAR 文件:commons-logging-1.1.3.jar
,cos-05Nov2002.jar
和stripes-1.6.0.jar
。 此外,还有StripesResources.properties
文件,其中包含各种消息。
简单 Stripes 应用
第一个应用显示当前日期。 我们在 NetBeans 中创建一个新的 Web 应用。 我们选择 Tomcat 作为我们的 JSP/servlet 硬币容器。
图:项目文件
该项目包含三个文件:HelloActionBean.java
包含响应我们请求的代码,showDate.jsp
是作为响应发送回用户的视图,而web.xml
文件包含用于设置 Stripes 的配置。 在此应用中,我们不使用StripesResources.properties
。
图:项目库
这些是我们构建 Stripes 应用所需的库。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>com.zetcode.action</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>Hello.action</welcome-file>
</welcome-file-list>
</web-app>
在标准web.xml
部署描述符中,我们配置 Stripes。 我们指定 Stripes 在哪里寻找ActionBean
:在我们的例子中是com.zetcode.action
包。 欢迎文件是当我们请求主页时显示的文件。 Hello.action
指示应执行HelloActionBean
。
HelloActionBean.java
package com.zetcode.action;
import java.util.Date;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
public class HelloActionBean implements ActionBean {
private static final String VIEW = "/WEB-INF/jsp/showDate.jsp";
private ActionBeanContext context;
private Date date;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
@Override
public ActionBeanContext getContext() {
return context;
}
@DefaultHandler
public Resolution hello() {
this.date = new Date();
return new ForwardResolution(VIEW);
}
}
HelloActionBean
处理请求,并以向前解析的方式响应 JSP 页面。
private static final String VIEW = "/WEB-INF/jsp/showDate.jsp";
该视图是showDate.jsp
文件。
private ActionBeanContext context;
ActionBeanContext
封装有关当前请求的信息。 如果我们出于任何原因需要使用它,它提供对底层 Servlet API 的访问。
@DefaultHandler
public Resolution hello() {
this.date = new Date();
return new ForwardResolution(VIEW);
}
@DefaultHandler
注解为此动作 bean 设置了默认处理器。 它用当前日期填充date
属性,并返回一个新的ForwardResolution
。 分辨率转发到视图。
showDate.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Current date</title>
</head>
<body>
<h3>The date is ${actionBean.date}</h3>
</body>
</html>
这是用户的模板视图。 ${actionBean}
表达式引用指向此视图的操作 bean。 我们使用表达式来引用动作 bean 的date
属性。
$ curl localhost:8084/SimpleStripes/
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Current date</title>
</head>
<body>
<h3>The date is Thu Jun 02 14:13:01 CEST 2016</h3>
</body>
</html>
构建和部署应用之后,我们将使用curl
工具访问应用的主页。 该应用将响应一个包含当前日期的 HTML 页面。
Hello Stripes 应用
在第二个应用中,我们有一个简单的 HTML 表单。 用户在文本框中指定其名称。 该应用以问候回应。 验证用于确保用户已在文本字段中输入了内容。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>com.zetcode.action</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在web.xml
文件中,我们将index.jsp
文件设置为欢迎文件。
index.jsp
<%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Enter your name</title>
</head>
<body>
<stripes:form beanclass="com.zetcode.action.HelloActionBean">
<stripes:errors/>
Enter your name:
<stripes:text name="userName"/>
<stripes:submit name="save" value="Submit"/>
</stripes:form>
</body>
</html>
index.jsp
包含一个简单的 HTML 表单。 Stripes 具有自己的标签。 <stripes:errors/>
显示验证错误。 如果我们未在该字段中写入任何文本,则会显示验证错误。 在<stripes:form>
标记中,我们指定应处理请求的操作 bean。 <stripes:text/>
创建一个文本字段。 创建的请求参数将自动映射到HelloActionBean
的userName
属性。
图:StripesResources.properties
StripesResources.properties
是 Stripes 框架的默认资源包。 它包含各种消息和标签的值。 样本文件包含在 Stripes 下载文件的lib
子目录中。 我们将文件放入源包中,未指定包。 (该文件应最终位于WEB-INF/classes
目录中。)
StripesResources.properties
...
validation.required.valueNotPresent={0} is a required field
...
当我们在文本字段中未输入任何内容并单击“提交”按钮时,将显示此错误消息。
HelloActionBean.java
package com.zetcode.action;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.validation.Validate;
public class HelloActionBean implements ActionBean {
private static final String VIEW = "/WEB-INF/jsp/greet.jsp";
private ActionBeanContext context;
@Validate(required=true)
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
@Override
public ActionBeanContext getContext() {
return context;
}
@DefaultHandler
public Resolution greet() {
return new ForwardResolution(VIEW);
}
}
单击提交按钮时,将执行HelloActionBean
。 request
参数自动绑定到其userName
属性。 默认处理器将转发到greet.jsp
视图。
@Validate(required=true)
private String userName;
@Validate
注解用于强制验证表单的用户名字段。 如果未输入任何值,则会显示一条错误消息。
图:验证错误消息
我们应用中的第二个视图是greet.jsp
。
greet.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Greeting</title>
</head>
<body>
<h3>Hello ${actionBean.userName}</h3>
</body>
</html>
greet.jsp
显示给用户的问候消息。 通过${actionBean.userName}
表达式,我们获得了用户名。
图:问候语
该应用以一条简单消息响应。
在本教程中,我们使用 Stripes Web 框架创建了两个简单的 Web 应用。 您可能也对 ZetCode 的 Java 教程,验证过滤器教程, Java MVC 教程, Play 框架简介和 Spark Java, Stripes,MyBatis & Derby 教程或 EJB 教程感兴趣。
使用 Stripes 的 Java webapp,MyBatis,& Derby
原文:http://zetcode.com/java/stripesmybatisderby/
在本教程中,我们使用 Stripes,MyBatis 和 Derby 创建一个 Java Web 应用。 我们使用 NetBeans 来构建应用。 Apache Tomcat 用作 JSP 和 servlet 容器。 可从作者的 Github 仓库中获得项目源。
Stripes 是一个开源的轻量级 Java Web 应用框架。 Stripes 的目标是使 Java 中基于 Servlet/JSP 的 Web 开发变得简单,直观和直接。 Stripes 是基于动作的 MVC(模型视图控制器)框架。 它运行在 JEE Web 容器中,使用最少的配置文件,并具有灵活和简单的参数绑定。
MyBatis 是 Java 持久性框架,使用 XML 描述符或注释将对象与存储过程或 SQL 语句耦合。 与 ORM 框架不同,MyBatis 不会将 Java 对象映射到数据库表,而是将 Java 方法映射到 SQL 语句。 MyBatis 允许使用所有数据库功能,例如存储过程,视图,任何复杂性和供应商专有功能的查询。
Derby 是用 Java 编写的关系数据库管理系统。 Oracle 以 Java DB 的名义分发相同的二进制文件。 Derby 的占用空间很小,约为 2MB。 Derby 使用的数据库格式是可移植的且与平台无关。
图书应用
我们在 NetBeans 中创建一个新的 Web 应用。 在应用中,我们将能够将新书添加到数据库中,通过它们的 ID 选择单个书,然后选择表中的所有书。 该项目需要 Stripes,MyBatis 和 JSTL 库。 前三个 JAR 是 MyBatis 库,后三个是 Stripes 的库。 我们必须从他们的项目页面中删除他们。 JSTL JAR 随 NebBeans 一起提供。
图:项目库
在“NetBeans 服务”选项卡中,我们展开“数据库”节点,然后右键单击 Java DB 节点,然后选择“创建数据库”选项。 数据库名称将为books
,用户名和密码为app
和app
。
books.sql
CREATE TABLE Books(Id INTEGER NOT NULL PRIMARY KEY GENERATED ALWAYS AS IDENTITY
(START WITH 1, INCREMENT BY 1), Author VARCHAR(30), Title VARCHAR(60),
Published INTEGER, Remark VARCHAR(150));
INSERT INTO Books(Author, Title, Published, Remark) VALUES ('Leo Tolstoy', 'War and Peace', 1869, 'Napoleonic wars');
INSERT INTO Books(Author, Title, Published, Remark) VALUES ('Leo Tolstoy', 'Anna Karenina', 1878, 'Greatest book of love');
INSERT INTO Books(Author, Title, Published, Remark) VALUES ('Jeff Prosise', 'Programming Windows with MFC', 1999, 'Classic book about MFC');
INSERT INTO Books(Author, Title, Published, Remark) VALUES ('Tom Marrs', 'JBoss at Work', 2005, 'JBoss practical guide');
INSERT INTO Books(Author, Title, Published, Remark) VALUES ('Debu Panda', 'EJB3 in Action', 2007, 'Introduction to Enterprice Java Beans');
我们创建一个与创建的数据库的新数据库连接,并执行此 SQL 代码。 我们有一个Books
表,其中包含几本书。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<filter>
<display-name>Stripes Filter</display-name>
<filter-name>StripesFilter</filter-name>
<filter-class>net.sourceforge.stripes.controller.StripesFilter</filter-class>
<init-param>
<param-name>ActionResolver.Packages</param-name>
<param-value>com.zetcode.action</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter-mapping>
<filter-name>StripesFilter</filter-name>
<servlet-name>StripesDispatcher</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>StripesDispatcher</servlet-name>
<servlet-class>net.sourceforge.stripes.controller.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>StripesDispatcher</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
在web.xml
部署描述符中,我们设置了 Stripes 框架。 index.jsp
文件是默认的主页文件。 在com.zetcode.action
中,有ActionBeans
。
resources
在resources
目录中,我们有三个文件。 通过右键单击项目文件并选择“属性”来创建resources
目录。 在源类别中,我们添加一个新的源包文件夹。
图:资源
BookMapper.xml
和mybatis-config.xml
是 MyBatis 使用的 XML 文件。 StripesResources.properties
是 Stripes 框架的默认资源束文件。 它包含应用的错误消息和标签。 它位于 Stripes 下载文件的lib
子目录中。
表示层
表示层由六个 JSP 页面组成。 index.jsp
是应用的默认主页。 addBook.jsp
是用于向数据库添加新书的页面,findBook.jsp
是用于通过其 ID 查找书的页面。 将书籍成功插入数据库后,bookAdded.jsp
显示一条消息,showOneBook.jsp
显示选定的书籍,showAllBooks.jsp
显示数据库中的所有书籍。
index.jsp
<%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Welcome page</title>
</head>
<body>
<stripes:link href="addBook.jsp">
Add a new book
</stripes:link>
<stripes:link href="findBook.jsp">
Find one book
</stripes:link>
<stripes:link beanclass="com.zetcode.action.SelectAllBooksActionBean">
Show all books
</stripes:link>
</body>
</html>
index.jsp
包含指向两个 JSP 页面的 Stripes 链接,以添加一本新书并查找一本书,以及一个指向ActionBean
的链接以显示所有书。 <%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
声明 Stripes 使用的标签,包括<stripes:link>
。
findBook.jsp
<%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Find a book</title>
</head>
<body>
<stripes:form beanclass="com.zetcode.action.SelectOneBookActionBean">
<stripes:errors/>
Book ID:
<stripes:text name="bookId"/><br>
<stripes:submit name="save" value="Submit"/>
</stripes:form>
</body>
</html>
在findBook.jsp
中,我们有一个表格,可通过其 ID 查找图书。 该表单包含一个文本字段和一个“提交”按钮。 插入值已验证; 如果用户添加了无效值,则应用将返回错误消息。
addBook.jsp
<%@taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Add new book</title>
</head>
<body>
<stripes:form beanclass="com.zetcode.action.AddBookActionBean">
<stripes:errors/>
Author:
<stripes:text name="author"/><br>
Title:
<stripes:text name="title"/><br>
Year of publishing:
<stripes:text name="published"/><br>
Remark
<stripes:text name="remark"/><br>
<stripes:submit name="save" value="Submit"/>
</stripes:form>
</body>
</html>
addBook.jsp
将一本新书添加到数据库中。 它包含一个带有四个文本字段的表单,用于书籍对象。 该 ID 由 Derby 数据库自动生成。
bookAdded.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Book added</title>
</head>
<body>
<h3>Book added to database</h3>
</body>
</html>
将新书添加到数据库后,应用返回bookAdded.jsp
,其中包含一条简单消息。
showAllBooks.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Show all books</title>
</head>
<body>
<h3>All books</h3>
<table>
<thead>
<tr>
<th>Id</th>
<th>Author</th>
<th>Title</th>
<th>Published</th>
<th>Remark</th>
</tr>
</thead>
<c:forEach items="${actionBean.books}" var='book'>
<tr>
<td>
<c:out value="${book.id}"/>
</td>
<td>
<c:out value="${book.author}"/>
</td>
<td>
<c:out value="${book.title}"/>
</td>
<td>
<c:out value="${book.published}"/>
</td>
<td>
<c:out value="${book.remark}"/>
</td>
</tr>
</c:forEach>
</table>
</body>
</html>
showAllBooks.jsp
显示从数据库检索到的所有书籍。 <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
指令声明了 JSTL 核心标记,包括<c:forEach>
和<c:out>
。 可以使用${actionBean}
表达式访问返回的数据,其中actionBean
是转发视图(即此 JSP 页面)的操作 bean。 在这种情况下,操作 bean 是SelectAllBooksActionBean
。
showOneBook.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Show one book</title>
</head>
<body>
<h3>A book</h3>
<table>
<thead>
<tr>
<th>Id</th>
<th>Author</th>
<th>Title</th>
<th>Published</th>
<th>Remark</th>
</tr>
</thead>
<tr>
<td>
<c:out value="${actionBean.book.id}"/>
</td>
<td>
<c:out value="${actionBean.book.author}"/>
</td>
<td>
<c:out value="${actionBean.book.title}"/>
</td>
<td>
<c:out value="${actionBean.book.published}"/>
</td>
<td>
<c:out value="${actionBean.book.remark}"/>
</td>
</tr>
</table>
</body>
</html>
showOneBook.jsp
显示一本书,我们通过其 ID 找到了它。
Book
bean
Book
bean 是一个 Java 类,代表我们应用的域对象-一本书。
Book.java
package com.zetcode.bean;
public class Book {
private Long id;
private String author;
private String title;
private int yearPublished;
private String remark;
public Book() {};
public Book(String author, String title, int published,
String remark) {
this.author = author;
this.title = title;
this.yearPublished = published;
this.remark = remark;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getPublished() {
return yearPublished;
}
public void setPublished(int published) {
this.yearPublished = published;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
Bean 具有必要的属性以及获取器和设置器方法。 请注意,还必须添加一个空的构造器。
ActionBean
Stripes 的ActionBean
是一个对象,该对象接收在请求中提交的数据并处理用户的输入。 它既定义了表单的属性,又定义了表单的处理逻辑。 最后,它向用户返回一个视图。 在我们的应用中,我们有三个动作 bean:AddBookActionBean
,SelectAllBooksActionBean
和SelectOneBookActionBean
。 它们每个代表在应用中要执行的某些操作。
图:动作 Bean
我们将动作 bean 放入com.zetcode.action
包中。
AddBookActionBean.java
package com.zetcode.action;
import com.zetcode.bean.Book;
import com.zetcode.service.BookService;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.validation.Validate;
public class AddBookActionBean implements ActionBean {
private static final String VIEW = "/bookAdded.jsp";
private ActionBeanContext context;
@Validate(required = true)
private String author;
@Validate(required = true)
private String title;
@Validate(required = true)
private int yearPublished;
@Validate(required = true)
private String remark;
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getYearPublished() {
return yearPublished;
}
public void setYearPublished(int yearPublished) {
this.yearPublished = yearPublished;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
@DefaultHandler
public Resolution addBook() {
Book book = new Book(this.author, this.title,
this.yearPublished, this.remark);
BookService.saveBook(book);
return new ForwardResolution(VIEW);
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
@Override
public ActionBeanContext getContext() {
return context;
}
}
在我们填写表格以添加新书后,将调用AddBookActionBean
。 动作 Bean 自动将请求属性绑定到其自己的属性。
private static final String VIEW = "/bookAdded.jsp";
书籍成功保存到数据库后,AddBookActionBean
返回bookAdded.jsp
页面。
@Validate(required=true)
private String author;
@Validate(required=true)
private String title;
...
使用@Validate
注解,我们为 HTML 字段提供了验证服务。 这些字段不能为空,并且必须与正确的数据类型匹配。
图:验证
如果发布年份具有非整数字符,则“提交”操作将失败。
@DefaultHandler
public Resolution addBook() {
Book book = new Book(this.author, this.title,
this.yearPublished, this.remark);
BookService.saveBook(book);
return new ForwardResolution(VIEW);
}
@DefaultHandler
注解指定此操作 bean 的默认处理器方法。 (可以定义多个处理器。)处理器创建Book
bean,调用BookService.saveBook()
并转发到适当的视图。
SelectOneBookActionBean.java
package com.zetcode.action;
import com.zetcode.bean.Book;
import com.zetcode.service.BookService;
import java.io.IOException;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
import net.sourceforge.stripes.validation.Validate;
public class SelectOneBookActionBean implements ActionBean {
private static final String VIEW = "/showOneBook.jsp";
private ActionBeanContext context;
private Book book;
@Validate(required=true)
private Long bookId;
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
public Long getBookId() {
return bookId;
}
public void setBookId(Long bookId) {
this.bookId = bookId;
}
@DefaultHandler
public Resolution showOneBook() throws IOException {
this.book = BookService.getBook(bookId);
return new ForwardResolution(VIEW);
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
@Override
public ActionBeanContext getContext() {
return context;
}
}
单击findBook.jsp
中的“提交”按钮后,将调用SelectOneBookActionBean
。
@DefaultHandler
public Resolution showOneBook() throws IOException {
this.book = BookService.getBook(bookId);
return new ForwardResolution(VIEW);
}
默认处理器调用BookService.getBook()
方法,然后转发到视图。
SelectAllBooksActionBean.java
package com.zetcode.action;
import com.zetcode.bean.Book;
import com.zetcode.service.BookService;
import java.util.List;
import net.sourceforge.stripes.action.ActionBean;
import net.sourceforge.stripes.action.ActionBeanContext;
import net.sourceforge.stripes.action.DefaultHandler;
import net.sourceforge.stripes.action.ForwardResolution;
import net.sourceforge.stripes.action.Resolution;
public class SelectAllBooksActionBean implements ActionBean {
private static final String VIEW = "/showAllBooks.jsp";
private ActionBeanContext context;
private List<Book> books;
public List<Book> getBooks() {
return books;
}
public void setBooks(List<Book> books) {
this.books = books;
}
@DefaultHandler
public Resolution showAll() {
this.books = BookService.getAllBooks();
return new ForwardResolution(VIEW);
}
@Override
public void setContext(ActionBeanContext context) {
this.context = context;
}
@Override
public ActionBeanContext getContext() {
return context;
}
}
SelectAllBooksActionBean
负责从数据库中选择所有书籍。
@DefaultHandler
public Resolution showAll() {
this.books = BookService.getAllBooks();
return new ForwardResolution(VIEW);
}
调用BookService.getAllBooks()
完成该工作。
服务
BookService
包含与数据库通信的方法。
BookService.java
package com.zetcode.service;
import com.zetcode.bean.Book;
import com.zetcode.persistence.MyBatisDAO;
import java.util.List;
public class BookService {
public static void saveBook(Book book) {
MyBatisDAO mbd = new MyBatisDAO();
mbd.saveBook(book);
}
public static List<Book> getAllBooks() {
MyBatisDAO mbd = new MyBatisDAO();
List<Book> books = mbd.findAll();
return books;
}
public static Book getBook(Long id) {
MyBatisDAO mbd = new MyBatisDAO();
Book book = mbd.findBook(id);
return book;
}
}
DAO 模式用于使示例更易于移植。
public static List<Book> getAllBooks() {
MyBatisDAO mbd = new MyBatisDAO();
List<Book> books = mbd.findAll();
return books;
}
getAllBooks()
方法创建MyBatisDAO
并调用其findAll()
方法。 它返回检索到的书的列表。
DAO
数据访问对象(DAO) 模式用于将底层数据访问 API 或操作与高层业务服务分开。
BookDAO.java
package com.zetcode.persistence;
import com.zetcode.bean.Book;
import java.util.List;
public interface BookDAO {
public void saveBook(Book book);
public Book findBook(Long id);
public List<Book> findAll();
}
访问数据的方法在BookDAO
接口中定义。 当我们根据该接口进行编程时,代码的耦合较少。
MyBatisDAO.java
package com.zetcode.persistence;
import com.zetcode.bean.Book;
import com.zetcode.util.ServiceLocator;
import java.util.List;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
public class MyBatisDAO implements BookDAO {
@Override
public void saveBook(Book book) {
SqlSession session = null;
try {
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
session.insert("insertBook", book);
session.commit();
} finally {
if (session != null) {
session.close();
}
}
}
@Override
public Book findBook(Long id) {
SqlSession session = null;
Book book = null;
try {
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
book = session.selectOne("selectBook", id);
} finally {
if (session != null) {
session.close();
}
}
return book;
}
@Override
public List<Book> findAll() {
SqlSession session = null;
List<Book> retrieveList = null;
try {
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
retrieveList = session.selectList("selectAllBooks");
} finally {
if (session != null) {
session.close();
}
}
return retrieveList;
}
}
MyBatisDAO
是BookDAO
接口的具体实现。 如果基础数据源发生更改,我们可以轻松地切换到新的 DAO 实现。
@Override
public void saveBook(Book book) {
SqlSession session = null;
try {
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
session.insert("insertBook", book);
session.commit();
} finally {
if (session != null) {
session.close();
}
}
}
saveBook()
方法将一本新书保存到数据库中。 创建一个SqlSessionFactory
来产生一个SqlSession
,这是使用 MyBatis 的主要 Java 接口。 工厂的创建委托给ServiceLocator
。 会话的insert()
方法使用给定的参数对象执行插入语句。 commit()
方法将更改提交到数据库。
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
book = session.selectOne("selectBook", id);
为了选择一本书,我们将id
参数传递给会话的selectOne()
方法。
SqlSessionFactory factory = ServiceLocator.getSessionFactory();
session = factory.openSession();
retrieveList = session.selectList("selectAllBooks");
selectList()
方法返回书籍对象列表。
ServiceLocator.java
package com.zetcode.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class ServiceLocator {
public static SqlSessionFactory getSessionFactory() {
InputStream inputStream = null;
SqlSessionFactory sqlSessionFactory = null;
try {
String resource = "mybatis-config.xml";
inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException ex) {
Logger.getLogger(ServiceLocator.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException ex) {
Logger.getLogger(ServiceLocator.class.getName()).log(Level.WARNING, null, ex);
}
}
return sqlSessionFactory;
}
}
ServiceLocator
从提供的配置文件中构建SqlSessionFactory
。 该工厂随后用于生产SqlSession
,这是与 MyBatis 进行通信的主要接口。
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Book" type="com.zetcode.bean.Book"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="url" value="jdbc:derby://localhost:1527/books"/>
<property name="username" value="app"/>
<property name="password" value="app"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="BookMapper.xml"/>
</mappers>
</configuration>
在mybatis-config.xml
文件中,我们创建一本新书Book
类型,定义 Derby 的数据源,并指定映射器文件。
BookMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zetcode">
<select id="selectAllBooks" resultType="Book">
SELECT * FROM Books
</select>
<select id="selectBook" parameterType="long" resultType="Book">
SELECT * FROM Books WHERE Id = #{id}
</select>
<insert id="insertBook" parameterType="Book" statementType="PREPARED">
INSERT INTO Books(Author, Title, Published, Remark) VALUES
(#{author}, #{title}, #{published}, #{remark})
</insert>
</mapper>
在BookMapper.xml
中,我们有应用中使用的 SQL 命令。 它具有两个选择和一个插入命令。
<select id="selectAllBooks" resultType="Book">
SELECT * FROM Books
</select>
注意,对于结果类型,我们不定义集合,而是定义集合项的类型。
图:显示所有书籍
屏幕截图显示了从示例数据库中选择的所有书籍。
在本教程中,我们使用 Stripes,MyBatis 和 Derby 创建了一个 Web 应用。 我们使用了三层 DAO 软件模式。 NetBeans 用于构建应用。 ZetCode 具有以下相关教程: Derby 教程, Java 教程, Stripes 教程和 EJB 简介。
EclipseLink 简介
原文:http://zetcode.com/java/eclipselink/
在本教程中,我们学习 EclipseLink 的基础。 在示例中,我们使用 Derby 和 Spring Boot。 这些项目是使用 NetBeans 构建的。 ZetCode 拥有用于 MySQL Java 的完整电子书: MySQL Java 编程电子书。
EclipseLink
EclipseLink 是来自 Eclipse Foundation 的开源 Eclipse Persistence Services 项目。 该软件提供了一个可扩展的框架,该框架允许 Java 开发者与各种数据服务进行交互,包括数据库,Web 服务,对象 XML 映射和企业信息系统。 EclipseLink 基于 TopLink 产品,Oracle 从该产品中贡献了源代码来创建 EclipseLink 项目。 EclipseLink 是 Java Persistence API 的参考实现。
Java 持久性 API (JPA) 是 Java 应用编程接口规范,它描述了使用 Java 的应用中关系数据的管理。 Java 持久性查询语言(JPQL) 是独立于平台的面向对象的查询语言。 它是 JPA 规范的一部分。 JPQL 用于对关系数据库中存储的实体进行查询。 它在很大程度上受到 SQL 的启发,其查询在语法上类似于 SQL 查询,但是针对 JPA 实体对象而不是直接针对数据库表进行操作。
实体是 Java 类,将与 JPA 保持在一起。 它必须用javax.persistence.Entity
注解修饰。 此外,它必须具有@Id
注解和@GeneratedValue
,注解定义实体的主键的唯一 ID,该@GeneratedValue
定义生成主键的策略。 @Table
注解指定实体映射到的数据库表。
persistence.xml
是 JPA 中的标准配置文件。 它必须包含在包含实体 bean 的 JAR 文件内的META-INF
目录中。 在此文件中,我们定义了持久性单元,这些持久性单元定义了由应用中的实体管理器实例管理的所有实体类的集合。 EntityManager
是管理实体的持久状态的类。
EclipseLink 读取数据
第一个示例是 Java 命令行程序,该程序从Cars
表中检索所有行。 我们创建一个新的 NetBeans Java Maven 项目。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>Persistence</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.40</version>
</dependency>
</dependencies>
</project>
在pom.xml
文件中,我们定义了两个依赖项:eclipselink
和derbyclient
。
图:创建persistence.xml
文件
要创建persistence.xml
,请在项目文件上单击鼠标右键,选择“新建—其他”,然后选择“持久性”类别。 有一个持久性单元选项。
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="cars-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/testdb"/>
<property name="javax.persistence.jdbc.user" value="app"/>
<property name="javax.persistence.jdbc.password" value="app"/>
<property name="javax.persistence.schema-generation.database.action"
value="drop-and-create"/>
<property name="javax.persistence.sql-load-script-source" value="META-INF/sql/data.sql"/>
</properties>
</persistence-unit>
</persistence>
在persistence.xml
文件中,我们定义了一个名为cars-pu
的持久性单元。 我们定义一个持久性供应器,它是一个 Derby 数据库。
<property name="javax.persistence.schema-generation.database.action"
value="drop-and-create"/>
设置此属性后,EclipseLink 将删除并创建数据库表。 Cars
表是从提供的元数据创建的。
<property name="javax.persistence.sql-load-script-source" value="META-INF/sql/data.sql"/>
在这里,我们指定 SQL 文件,该文件将数据填充到表中。
图:persistence.xml
文件
persistence.xml
文件位于META-INF
子目录中
data.sql
INSERT INTO CARS(Name, Price) VALUES('Audi', 52642);
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000);
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000);
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000);
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000);
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400);
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600);
这是用八行填充Cars
表的 SQL。
Car.java
package com.zetcode;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name="Cars")
public class Car implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String name;
private int price;
public Long getId() {
return Id;
}
public void setId(Long Id) {
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
这是我们的实体类。 它用@Entity
注解修饰。
@Table(name="Cars")
@Table
注解引用 Derby 数据库中的Cars
表。
DBClient.java
package com.zetcode;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.Query;
public class DBClient {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("cars-pu");
EntityManager eman = emf.createEntityManager();
try {
String sql = "SELECT c FROM Car c";
Query query = eman.createQuery(sql);
List<Car> cars = query.getResultList();
for (Car car : cars) {
System.out.printf("%d ", car.getId());
System.out.printf("%s ", car.getName());
System.out.println(car.getPrice());
}
} finally {
eman.close();
emf.close();
}
}
}
这是一个 Java 控制台应用,它在实体管理器的帮助下从Cars
表中检索所有行。
EntityManagerFactory emf = Persistence.createEntityManagerFactory("cars-pu");
在 JEE 容器之外使用 EclipseLink 之类的 JPA 供应器时,我们必须通过EntityManagerFactory
创建一个实体管理器。 使用持久性单元名称作为参数,通过Persistence.createEntityManagerFactory()
方法创建工厂。
EntityManager eman = emf.createEntityManager();
从EntityManagerFactory
中,我们使用createEntityManager()
方法创建一个EntityManager
。 创建的EntityManager
是应用管理的实体管理器。
String sql = "SELECT c FROM Car c";
看起来像 SQL 代码,但不是。 它是 Java 持久性查询语言(JPQL)语句的示例。 它从数据库表返回所有Car
实体。
Query query = eman.createQuery(sql);
使用createQuery()
方法创建Query
对象。
List<Car> cars = query.getResultList();
从查询对象中,我们获得Car
对象的列表。
} finally {
eman.close();
emf.close();
}
对于由应用管理的实体管理器,我们必须显式关闭资源。
1 Audi 52642
2 Mercedes 57127
3 Skoda 9000
4 Volvo 29000
5 Bentley 350000
6 Citroen 21000
7 Hummer 41400
8 Volkswagen 21600
启动 Derby,构建应用并执行它之后,我们得到此输出。
EclipseLink 保存新行
在第二个示例中,我们将一个新的汽车对象保存到数据库中。 pom.xml
,persistence.xml
和Car.java
与前面示例中使用的相同。
DBClient2.java
package com.zetcode;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class DBClient2 {
public static void main(String[] args) {
EntityManagerFactory efact = Persistence.createEntityManagerFactory("cars-pu");
EntityManager eman = efact.createEntityManager();
try {
eman.getTransaction().begin();
Car car = new Car();
car.setName("Toyota");
car.setPrice(26700);
eman.persist(car);
eman.getTransaction().commit();
} finally {
eman.close();
efact.close();
}
}
}
该示例创建一个新的汽车对象并将其保存在数据库中。
eman.getTransaction().begin();
JEE 容器外部不提供自动事务管理; 因此,我们必须手动创建一个新事务。
Car car = new Car();
car.setName("Toyota");
car.setPrice(26700);
创建一个新的汽车实体。 该 ID 将由 Derby 生成。
eman.persist(car);
persist()
方法将实体保存到数据库中。
eman.getTransaction().commit();
事务已提交。
EclipseLink 的 Spring Boot 示例
在此示例中,我们将 EclipseLink 集成到 Spring Boot 应用中。 Spring 是流行的 Java 应用框架。 Spring Boot 是 Spring 的解决方案,用于创建独立的,生产级的基于 Spring 的应用。 这是一个新的解决方案,可以以最少的工作量创建 Spring 应用。
以下示例的资源也可从作者的 Github 仓库中获得。
图:Spring Boot 项目
这是 NetBeans 中的 Spring Boot 项目。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootEclipseLink</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.3.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>eclipselink</artifactId>
<version>2.6.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<exclusions>
<exclusion>
<artifactId>hibernate-entitymanager</artifactId>
<groupId>org.hibernate</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>
org.eclipse.persistence.jpa.modelgen.processor
</artifactId>
<version>2.5.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
这是 Maven 项目文件。 我们具有 EclipseLink,Derby,Hibernate Validator 和 Spring Boot 的依赖项。
application.properties
# Derby
spring.datasource.driverClassName=org.apache.derby.jdbc.ClientDriver
spring.datasource.url=jdbc:derby://localhost:1527/testdb
spring.datasource.username=app
spring.datasource.password=app
在application.properties
文件中,我们定义了 Derby 的数据源。
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="cars-pu" transaction-type="RESOURCE_LOCAL">
<provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.jdbc.url" value="jdbc:derby://localhost:1527/testdb"/>
<property name="javax.persistence.jdbc.user" value="app"/>
<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.ClientDriver"/>
<property name="javax.persistence.jdbc.password" value="app"/>
<property name="javax.persistence.schema-generation.database.action"
value="drop-and-create"/>
<property name="javax.persistence.sql-load-script-source" value="META-INF/sql/data.sql"/>
</properties>
</persistence-unit>
</persistence>
在persistence.xml
文件中,我们创建一个持久性单元。 它称为cars-pu
。 我们还让 EclipseLink 为我们创建一个数据库表。
data.sql
INSERT INTO CARS(Name, Price) VALUES('Audi', 52642)
INSERT INTO CARS(Name, Price) VALUES('Mercedes', 57127)
INSERT INTO CARS(Name, Price) VALUES('Skoda', 9000)
INSERT INTO CARS(Name, Price) VALUES('Volvo', 29000)
INSERT INTO CARS(Name, Price) VALUES('Bentley', 350000)
INSERT INTO CARS(Name, Price) VALUES('Citroen', 21000)
INSERT INTO CARS(Name, Price) VALUES('Hummer', 41400)
INSERT INTO CARS(Name, Price) VALUES('Volkswagen', 21600)
EclipseLink 将使用此 SQL 文件用数据填充自动创建的表。
AppConf.java
package com.zetcode.conf;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AppConf {
@Bean
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return entityManagerFactory.createEntityManager();
}
@Bean
public EntityManagerFactory createEntityManagerFactory() {
return Persistence.createEntityManagerFactory("cars-pu");
}
}
在AppConf
中创建了两个 bean:EntityManagerFactory
和EntityManager
。 EntityManager
将被注入CarsServiceImpl
中。
Car.java
package com.zetcode.bean;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
@Entity
@Table(name="Cars")
@NamedQueries({
@NamedQuery(name = "Car.findAll", query = "SELECT c FROM Car c"),
@NamedQuery(name = "Car.findByName", query = "SELECT c FROM Car c WHERE c.name = :name")
})
public class Car {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long Id;
private String name;
private int price;
public Long getId() {
return Id;
}
public void setId(Long Id) {
this.Id = Id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
这是我们的Entity
bean。
@NamedQueries({
@NamedQuery(name = "Car.findAll", query = "SELECT c FROM Car c"),
@NamedQuery(name = "Car.findByName", query = "SELECT c FROM Car c WHERE c.name = :name")
})
该类用命名查询注解,这是 JPQL 语句,用于检索所有汽车对象并按名称查找汽车。
CarsService.java
package com.zetcode.service;
import com.zetcode.bean.Car;
import java.util.List;
public interface CarsService {
public void saveCar(Car car);
public Car findCarByName(String name);
public List<Car> findAll();
}
在CarsService
接口中,我们定义了通过实体管理器访问数据库的合同方法。
CarsServiceImpl.java
package com.zetcode.service;
import com.zetcode.bean.Car;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.springframework.stereotype.Component;
@Component
public class CarsServiceImpl implements CarsService {
@PersistenceContext
EntityManager em;
@Override
public void saveCar(Car car) {
em.getTransaction().begin();
em.persist(car);
em.getTransaction().commit();
}
@Override
public Car findCarByName(String name) {
Query query = em.createNamedQuery("Car.findByName");
query.setParameter("name", name);
Car car = (Car) query.getSingleResult();
return car;
}
@Override
public List<Car> findAll() {
Query query = em.createNamedQuery("Car.findAll");
List<Car> cars = query.getResultList();
return cars;
}
}
CarsServiceImpl
是一个业务服务类,它实现以下方法:保存汽车,按汽车名称查找汽车,以及从数据库中检索所有汽车。
@PersistenceContext
EntityManager em;
将EntityManager
与@PersistenceContext
注解一起注入到类中。
@Override
public void saveCar(Car car) {
em.getTransaction().begin();
em.persist(car);
em.getTransaction().commit();
}
saveCar()
方法通过EntityManager
的persist()
方法将汽车保存到数据库中。 persist()
方法放置在手动创建的事务中。
@Override
public Car findCarByName(String name) {
Query query = em.createNamedQuery("Car.findByName");
query.setParameter("name", name);
Car car = (Car) query.getSingleResult();
return car;
}
findCarByName()
方法通过其名称查找汽车。 createNamedQuery()
创建一个命名查询; 它引用Car
实体类中定义的命名查询。 使用setParameter()
方法将参数设置为命名查询。
@Override
public List<Car> findAll() {
Query query = em.createNamedQuery("Car.findAll");
List<Car> cars = query.getResultList();
return cars;
}
创建一个命名查询Car.findAll
。 查询的getResultList()
返回已检索汽车的列表。
MyRunner.java
package com.zetcode.client;
import com.zetcode.bean.Car;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Component;
import com.zetcode.service.CarsService;
@Component
public class MyRunner implements CommandLineRunner {
@Autowired
private CarsService crs;
@Override
public void run(String... args) throws Exception {
try {
Car car = crs.findCarByName("Citroen");
System.out.printf("ID: %d%n", car.getId());
System.out.printf("Name: %s%n", car.getName());
System.out.printf("Price: %d%n", car.getPrice());
} catch (EmptyResultDataAccessException e) {
System.out.println("Car was not found");
}
List<Car> cars = crs.findAll();
for (Car car: cars) {
System.out.printf("%d ", car.getId());
System.out.printf("%s ", car.getName());
System.out.println(car.getPrice());
}
}
}
MyRunner
是 Spring Boot CLI 应用的命令行运行程序。 我们寻找雪铁龙汽车,并从数据库中检索所有汽车。
@Autowired
private CarsService crs;
CarsService
bean 被注入到MyRunner
类中。
Car car = crs.findCarByName("Citroen");
该服务的findCarByName()
寻找一辆名为雪铁龙的汽车。
List<Car> cars = crs.findAll();
服务的findAll()
方法从数据库中检索所有汽车。
SpringDBCLIApp.java
package com.zetcode.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration
@ComponentScan(basePackages="com.zetcode")
public class SpringDBCLIApp {
public static void main(String[] args) {
SpringApplication.run(SpringDBCLIApp.class, args);
}
}
SpringDBCLIApp
设置 Spring Boot CLI 应用。
在本教程中,我们介绍了 EclipseLink JPA 供应器。 ZetCode 具有以下相关教程: Spring JdbcTemplate
教程, Hibernate Derby 教程, Java 教程和 EJB 简介。
Java 中的数据源
原文:http://zetcode.com/java/datasource/
在本教程中,我们学习如何在 Java 中设置数据源。 我们使用 MySQL 数据库系统。 ZetCode 拥有用于 MySQL Java 的完整电子书: MySQL Java 编程电子书。
我们使用 MySQL Connector/J 驱动程序。 它是 MySQL 的官方 JDBC 驱动程序。
用 Java 创建到数据库的连接有两种基本方法:a)使用驱动程序管理器,b)使用数据源。 与驱动程序管理器相比,数据源具有几个优点:
- 它支持分布式事务
- 它提供了一种连接池技术
- 它可以由服务器(即应用外部)进行管理
当在 Java 类中创建和关闭连接时,驱动程序管理器会影响应用性能。 驱动程序管理器可用于简单的测试应用中。 对于复杂的应用,始终建议使用数据源。 请参阅 MySQL Java 教程,以了解如何在 Java 应用中使用驱动程序管理器。
通常,将基于 Java 命名和目录接口(JNDI)API 向实现数据源接口的对象注册命名服务。
JDBC
JDBC 是 Java 编程语言的 API,用于定义客户端如何访问数据库。 它提供了查询和更新数据库中数据的方法。 JDBC 面向关系数据库。 从技术角度来看,API 是java.sql
包中的一组类。 要将 JDBC 与特定数据库一起使用,我们需要该数据库的 JDBC 驱动程序。
MySQL
MySQL 是领先的开源数据库管理系统。 它是一个多用户,多线程的数据库管理系统。 MySQL 在网络上特别流行。 MySQL 有两个版本:MySQL 服务器系统和 MySQL 嵌入式系统。
mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)
我们创建一个新的testdb
数据库。 在本教程中,我们只需要一个数据库对象。 我们将不使用表格。 我们将使用SELECT VERSION()
语句获取 MySQL 数据库的版本。
命令行应用
在此示例中,我们使用命令行 Java 应用连接到数据库。
图:项目结构
这就是 NetBeans 中项目结构的样子。
MysqlDataSource
是用于创建数据源的类。
db.properties
# mysql properties
mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/testdb
mysql.username=testuser
mysql.password=test623
这些是 MySQL 数据库的属性。 db.properties
文件位于此项目的src/resources
子目录中。
ComLineDSEx.java
package com.zetcode;
import com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource;
import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.PreparedStatement;
import java.util.Properties;
public class ComLineDSEx {
public static MysqlDataSource getMySQLDataSource() throws
FileNotFoundException, IOException {
Properties props = new Properties();
FileInputStream fis = null;
MysqlDataSource ds = null;
fis = new FileInputStream("src/resources/db.properties");
props.load(fis);
ds = new MysqlConnectionPoolDataSource();
ds.setURL(props.getProperty("mysql.url"));
ds.setUser(props.getProperty("mysql.username"));
ds.setPassword(props.getProperty("mysql.password"));
return ds;
}
public static void main(String[] args) throws IOException, SQLException {
Connection con = null;
PreparedStatement pst = null;
ResultSet rs = null;
MysqlDataSource ds = getMySQLDataSource();
try {
con = ds.getConnection();
pst = con.prepareStatement("SELECT VERSION()");
rs = pst.executeQuery();
if (rs.next()) {
String version = rs.getString(1);
System.out.println(version);
}
} finally {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
}
}
}
在此示例中,我们使用数据源连接到数据库并获取 MySQL 的版本。
fis = new FileInputStream("src/main/Resources/db.properties");
props.load(fis);
从具有FileInputStream
类的db.properties
文件中读取数据库属性。
ds = new MysqlConnectionPoolDataSource();
ds.setURL(props.getProperty("mysql.url"));
ds.setUser(props.getProperty("mysql.username"));
ds.setPassword(props.getProperty("mysql.password"));
创建MysqlConnectionPoolDataSource
并设置数据源属性。
con = ds.getConnection();
使用getConnection()
方法从数据源创建连接对象。
pst = con.prepareStatement("SELECT VERSION()");
创建一条 SQL 语句。 SELECT VERSION()
命令返回 MySQL 的版本。
rs = pst.executeQuery();
查询被执行。 它返回一个结果集。
if (rs.next()) {
String version = rs.getString(1);
System.out.println(version);
}
我们从结果集中获取第一个值,并将其打印到控制台。
} finally {
if (rs != null) {
rs.close();
}
if (pst != null) {
pst.close();
}
if (con != null) {
con.close();
}
}
最后,资源被释放。
Tomcat 中的 Web 应用
我们创建了一个 Web 应用,它将检索 MySQL 的版本。 该应用已部署在 Tomcat 上。
图:项目库
在我们的项目中,我们使用 JSTL 和 MySQL 驱动程序 JAR。 JavaServer Pages 标准标记库(JSTL) 是有用的 JSP 标记的集合,这些标记提供了许多 JSP 文件所共有的核心功能。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/TomcatDSEx">
<Resource name="jdbc/testdb"
auth="Container"
type="javax.sql.DataSource"
username="testuser"
password="test623"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/testdb"
maxActive="10"
maxIdle="4"/>
</Context>
对于 Tomcat Web 服务器,我们在context.xml
文件中创建一个新资源。 该文件位于META-INF
目录中。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<resource-ref>
<description>DB Connection</description>
<res-ref-name>jdbc/testdb</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
</web-app>
然后,在web.xml
文件中,创建对资源的引用。 在我们的应用中,我们将使用逻辑名称jdbc/testdb
引用数据源。
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>JSP Page</title>
</head>
<body>
<c:redirect url="/Version"/>
</body>
</html>
index.jsp
文件重定向到Version
Servlet。
showVersion.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>MySQL version</title>
</head>
<body>
MySQL version: <c:out value="${version}"/>
</body>
</html>
showVersion.jsp
是一个 UI 元素,用于显示从数据库检索的数据。
MySQL version: <c:out value="${version}"/>
JSTL 的<c:out>
标记用于输出响应的值。
Version.java
package com.zetcode.version;
import com.zetcode.version.service.DBVersionService;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "Version", urlPatterns = {"/Version"})
public class Version extends HttpServlet {
protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String page = "/showVersion.jsp";
String version = DBVersionService.getMySQLVersion();
request.setAttribute("version", version);
RequestDispatcher disp = getServletContext().getRequestDispatcher(page);
disp.forward(request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
processRequest(request, response);
}
@Override
public String getServletInfo() {
return "Returns version of MySQL";
}
}
Version
Servlet 调用服务方法来获取 MySQL 的版本。 返回的值设置为请求对象的属性。
String page = "/showVersion.jsp";
最后,Servlet 指向showVersion.jsp
文件。
String version = DBVersionService.getMySQLVersion();
调用服务方法来获取 MySQL 的版本。
request.setAttribute("version", version);
使用setAttribute()
方法将版本值设置为请求对象。
RequestDispatcher disp = getServletContext().getRequestDispatcher(page);
disp.forward(request, response);
我们调度到showVersion.jsp
文件。
DBVersionService.java
package com.zetcode.version.service;
import com.zetcode.version.Version;
import com.zetcode.version.util.ServiceLocator;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class DBVersionService {
public static String getMySQLVersion() {
String version = "no version";
DataSource ds = ServiceLocator.getDataSource("java:comp/env/jdbc/testdb");
Connection con = null;
try {
con = ds.getConnection();
Statement stm = con.createStatement();
ResultSet rs = stm.executeQuery("SELECT VERSION()");
if (rs.next()) {
version = rs.getString(1);
}
} catch (SQLException ex) {
Logger.getLogger(Version.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (con != null) {
try {
con.close();
} catch (SQLException ex) {
Logger.getLogger(DBVersionService.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
return version;
}
}
DBVersionService
是一个服务类,其中包含获取 MySQL 版本的方法。
DataSource ds = ServiceLocator.getDataSource("java:comp/env/jdbc/testdb");
数据源是使用ServiceLocator
类创建的。
con = ds.getConnection();
Statement stm = con.createStatement();
ResultSet rs = stm.executeQuery("SELECT VERSION()");
if (rs.next()) {
version = rs.getString(1);
}
在这里,我们有用于连接到数据库并执行 SQL 语句的 JDBC 代码。
ServiceLocator.java
package com.zetcode.version.util;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
public class ServiceLocator {
public static DataSource getDataSource(String jndiName) {
Context ctx = null;
DataSource ds = null;
try {
ctx = new InitialContext();
ds = (DataSource) ctx.lookup(jndiName);
} catch (NamingException ex) {
Logger.getLogger(ServiceLocator.class.getName()).log(Level.SEVERE, null, ex);
}
return ds;
}
}
ServiceLocator
通过其给定的 JNDI 名称查找数据源,并将其返回给调用方。
$ curl localhost:8084/TomcatDSEx/Version
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>MySQL version</title>
</head>
<body>
MySQL version: 5.5.49-0ubuntu0.14.04.1
</body>
</html>
该应用将响应一个包含 MySQL 版本的 HTML 页面。
这是 Java 教程中的数据源。 您可能也对 JDBI 教程, MyBatis 教程, SQL 查询标记教程或 MySQL 教程感兴趣。
JSTL 中的 SQL 查询标记
原文:http://zetcode.com/java/sqlquerytag/
在本教程中,我们将学习如何使用 JSTL 的 SQL 查询标记。
JSTL
JavaServer Pages 标准标记库(JSTL) 是有用的 JSP 标记的集合,这些标记提供了许多 JSP 文件所共有的核心功能。 <sql:query>
标记执行 SQL SELECT
语句,并将结果保存在范围变量中。
通常,不建议从 JSP 页面访问数据库。 但是,对于简单的应用和测试,它可能会很有用。 在我们的应用中,我们将使用 JSTL 的 SQL 查询标记从 MySQL 数据库检索数据。 该项目是使用 Maven 构建的。 我们将应用部署在 Tomcat 上。
创建一个 MySQL 数据库
首先,我们在 MySQL 中创建testdb
数据库和Cars
表。
cars_mysql.sql
DROP TABLE IF EXISTS Cars;
CREATE TABLE Cars(Id INT PRIMARY KEY AUTO_INCREMENT,
Name TEXT, Price INT) ENGINE=InnoDB;
INSERT INTO Cars(Name, Price) VALUES('Audi', 52642);
INSERT INTO Cars(Name, Price) VALUES('Mercedes', 57127);
INSERT INTO Cars(Name, Price) VALUES('Skoda', 9000);
INSERT INTO Cars(Name, Price) VALUES('Volvo', 29000);
INSERT INTO Cars(Name, Price) VALUES('Bentley', 350000);
INSERT INTO Cars(Name, Price) VALUES('Citroen', 21000);
INSERT INTO Cars(Name, Price) VALUES('Hummer', 41400);
INSERT INTO Cars(Name, Price) VALUES('Volkswagen', 21600);
这是在 MySQL 中创建Cars
表的 SQL。
要创建数据库和表,我们使用mysql
监视工具。
$ sudo service mysql start
MySQL 用sudo service mysql start
命令启动。
$ mysql -u testuser -p
我们使用mysql
监视器连接到数据库。
mysql> CREATE DATABASE testdb;
Query OK, 1 row affected (0.02 sec)
CREATE DATABASE
语句创建一个名为testdb
的新数据库。
mysql> USE testdb;
mysql> SOURCE cars_mysql.sql
使用source
命令,加载并执行cars_mysql.sql
文件。
mysql> SELECT * FROM Cars;
+----+------------+--------+
| Id | Name | Price |
+----+------------+--------+
| 1 | Audi | 52642 |
| 2 | Mercedes | 57127 |
| 3 | Skoda | 9000 |
| 4 | Volvo | 29000 |
| 5 | Bentley | 350000 |
| 6 | Citroen | 21000 |
| 7 | Hummer | 41400 |
| 8 | Volkswagen | 21600 |
+----+------------+--------+
8 rows in set (0.00 sec)
我们验证数据。 请参阅 MySQL 教程,以了解有关 MySQL 的更多信息。
使用 Maven 启动项目
Apache Maven 是一个软件项目管理和理解工具。
$ mvn archetype:generate -DgroupId=com.zetcode -DartifactId=SqlQueryTag
-DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false
使用maven-archetype-webapp
,我们创建了 Web 应用的框架。
$ cd SqlQueryTag/
$ tree
.
├── pom.xml
└── src
└── main
├── resources
└── webapp
├── index.jsp
└── WEB-INF
└── web.xml
5 directories, 3 files
Maven 创建了这个项目结构。
$ mkdir src/main/webapp/META-INF
$ touch src/main/webapp/META-INF/context.xml
我们创建一个META-INF
目录和context.xml
文件。
应用
应用连接到先前创建的Cars
表,并检索其所有行。 要连接到数据库表,我们使用<sql:query>
标签。
Maven 项目对象模型(POM)文件是保存在名为pom.xml
的文件中的 Maven 项目的 XML 表示。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SqlQueryTag</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>SqlQueryTag Maven Webapp</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
</dependency>
</dependencies>
<build>
<finalName>SqlQueryTag</finalName>
</build>
</project>
在pom.xml
文件中,我们声明 MySQL 驱动程序和 JSTL 库的依赖关系。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/SqlQueryTag" >
<Resource name="jdbc/testdb"
auth="Container"
type="javax.sql.DataSource"
username="testuser"
password="test623"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/testdb"
maxActive="10"
maxIdle="4"/>
</Context>
context.xml
文件是 Web 应用的 Tomcat 配置文件。 在context.xml
文件中,我们定义了一个数据源。 请参阅 Java 教程中的数据源以了解有关数据源的更多信息。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
我们提供了一个标准的部署描述符。 请注意,Maven 可能会创建与您的 JSTL JAR 不兼容的部署描述符。
index.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Cars</title>
</head>
<sql:query var="carsList" dataSource="jdbc/testdb">
SELECT * FROM Cars
</sql:query>
<body>
<div align="center">
<table border="1" cellpadding="2">
<h2>List of cars</h2>
<tr>
<th>Id</th>
<th>Name</th>
<th>Price</th>
</tr>
<c:forEach var="car" items="${carsList.rows}">
<tr>
<td><c:out value="${car.Id}" /></td>
<td><c:out value="${car.Name}" /></td>
<td><c:out value="${car.Price}" /></td>
</tr>
</c:forEach>
</table>
</div>
</body>
</html>
在index.jsp
文件中,我们连接到数据库,从Cars
表中检索数据,并将其显示在 HTML 表中。
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
我们需要声明 JSTL 核心和 sql 模块。
<sql:query var="carsList" dataSource="jdbc/testdb">
SELECT * FROM Cars
</sql:query>
使用<sql:query>
标记,执行SELECT * FROM Cars
语句。 数据存储在carsList
变量中。 数据源由dataSource
参数指定。
<c:forEach var="car" items="${carsList.rows}">
<tr>
<td><c:out value="${car.Id}" /></td>
<td><c:out value="${car.Name}" /></td>
<td><c:out value="${car.Price}" /></td>
</tr>
</c:forEach>
<c:forEach>
变量通过carsList
变量,<c:out>
输出当前值。
构建和部署
现在,我们将构建和部署该应用。
$ mvn package
我们建立项目。
$ mysql start/running, process 6030
$ $TOMCAT_HOME/bin/startup.sh
我们启动 MySQL 和 Tomcat。
$ cp target/SqlQueryTag.war $TOMCAT_HOME/webapps
我们部署应用。
图:展示汽车
我们在浏览器中导航到该应用,然后从数据库中获取数据。
这是 SQL 查询标记教程。 我们已经使用 JSTL,JSP,MySQL,Tomcat 和 Maven 构建了一个 Web 应用。 您可能还需要检查 JSTL forEach 标签,验证过滤器教程, JDBI 教程, MySQL 教程或 Apache Derby 教程。
Java 验证过滤器
原文:http://zetcode.com/java/validationfilter/
在本教程中,我们将展示如何验证用户在 Web 应用中输入的数据。 验证是一项常见的任务,并包含在 Java Web 框架(如 Stripes,Ninja 框架或 Play 框架)中。 在本教程中,我们将使用简单的自定义验证过滤器来验证数据。 来源可从作者的 Github 仓库中获得。
过滤器是一个对象,它对对资源的请求或对资源的响应(或两者)执行过滤任务。 过滤器以doFilter()
方法执行过滤。
过滤器可用于各种任务,例如认证,日志记录,数据压缩,图像转换或加密。 在我们的示例中,我们使用过滤器来验证输入数据。
在我们的应用中,我们有一个 HTML 表单,该表单接受用户的输入。 该表单具有两个输入标签:用户名和电子邮件。 输入正在使用过滤器进行验证。 为了验证电子邮件格式,我们使用 Apache Commons Validator。 该项目是使用 NetBeans IDE 中的 Maven 构建的。 我们将应用部署在 Tomcat 上。
图:结构
该图显示了 NetBeans 中的项目结构。 我们有三个 JSP 页面,两个 Java 类和两个 XML 配置文件。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>Validation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>Validation</name>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.5.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
这是pom.xml
构建文件。 它包含 JSTL 和 Apache Commons Validator 依赖关系的依赖关系。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/Validation"/>
在context.xml
文件中,我们指定应用的上下文路径。 它用于唯一标识应用。
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Validation</title>
</head>
<body>
<p>
Enter your name and email:
</p>
<form method="post" action="Greet">
Name: <input type="text" name="username"> <br>
Email: <input type="text" name="email"> <br>
<input type="submit" value="Submit">
</form>
</body>
</html>
index.jsp
是应用的入口点。 它具有带有两个字段的 HTML 表单。 在这些字段中输入的值将由应用验证。
<form method="post" action="Greet">
...
</form>
提交表单后,将调用Greet
Servlet。 在到达 Servlet 之前,过滤器将处理请求。
hello.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Greeting</title>
</head>
<body>
Hello <c:out value="${param.username}"/>! <br>
Your email is <c:out value="${param.email}"/>.
</body>
</html>
当输入数据通过验证测试时,将显示hello.jsp
页面。 显示输入的数据。
valError.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Error</title>
</head>
<body>
<p>
<c:out value="${errMsg}"/>
</p>
</body>
</html>
如果验证失败,则显示valError.jsp
。 它显示了存储在errMsg
属性中的错误消息。 该属性在验证过滤器中设置。
ValidationFilter.java
package com.zetcode.web;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.apache.commons.validator.routines.EmailValidator;
@WebFilter(filterName = "ValidationFilter", urlPatterns = {"/Greet"})
public class ValidationFilter implements Filter {
public ValidationFilter() { }
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
String erpg = "valError.jsp";
String userName = request.getParameter("username");
String email = request.getParameter("email");
boolean valid = EmailValidator.getInstance().isValid(email);
if (userName == null || "".equals(userName)
|| email == null || "".equals(email)) {
request.setAttribute("errMsg", "One or both fields are empty");
RequestDispatcher rd = request.getRequestDispatcher(erpg);
rd.include(request, response);
} else if (!valid) {
request.setAttribute("errMsg", "Email format not valid");
RequestDispatcher rd = request.getRequestDispatcher(erpg);
rd.include(request, response);
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() { }
@Override
public void init(FilterConfig filterConfig) { }
}
数据验证在ValidationFilter
类中执行。
@WebFilter(filterName = "ValidationFilter", urlPatterns = {"/Greet"})
@WebFilter
注解声明一个 servlet 过滤器。 过滤器将应用于指定的 URL 模式。 在我们的例子中,它在Greet
servlet 调用之前被调用。
public class ValidationFilter implements Filter {
过滤器实现Filter
接口。
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
...
}
实际工作以doFilter()
方法完成。
String userName = request.getParameter("username");
String email = request.getParameter("email");
通过getParameter()
方法,我们获得了 HTML 表单发送的数据。
boolean valid = EmailValidator.getInstance().isValid(email);
使用 Apache Commons Validator 的EmailValidator
,我们检查电子邮件格式的有效性。
if (userName == null || "".equals(userName)
|| email == null || "".equals(email)) {
request.setAttribute("errMsg", "One or both fields are empty");
RequestDispatcher rd = request.getRequestDispatcher(erpg);
rd.include(request, response);
} else if (!valid) {
request.setAttribute("errMsg", "Email format not valid");
RequestDispatcher rd = request.getRequestDispatcher(erpg);
rd.include(request, response);
} else {
chain.doFilter(request, response);
}
如果数据未能通过验证,则使用RequestDispatcher
将处理分派到错误页面。 否则,请求将继续其行进到目的地 servlet。
Greeting.java
package com.zetcode.web;
import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "Greeting", urlPatterns = {"/Greet"})
public class Greeting extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String page = "/hello.jsp";
RequestDispatcher disp = getServletContext().getRequestDispatcher(page);
disp.forward(request, response);
}
}
Greeting
servlet 仅将请求与RequestDispatcher
一起调度到hello.jsp
页面。
图:错误消息
如果电子邮件格式不正确,则应用将以错误消息响应。
这是验证过滤器教程。 我们已经使用 JSTL,JSP,Apache Commons Validator,Tomcat 和 Maven 构建了一个 Web 应用。 您可能还需要查看一些相关的教程: SQL 查询标记教程, Java 教程,读取 WAR 中的 CSV 文件和 Stripes 教程。
Hibernate 验证器
原文:http://zetcode.com/java/hibernatevalidator/
在本教程中,我们展示了如何使用 Hibernate 验证器来验证数据。 验证从用户那里收到的输入以保持数据完整性是应用逻辑的重要组成部分。 验证被合并到 Java Web 框架中,例如 Stripes,Ninja 框架或 Play 框架。
Bean 验证
Bean 验证是 Java EE 6 平台中引入的验证模型。 约束通过以 JavaBeans 组件的字段,方法或类上的注解形式的约束来支持 Bean 验证模型。 也可以使用 XML 验证描述符。
Hibernate 验证器定义
Hibernate 验证器是 Bean 验证的参考实现。 Hibernate Validator 允许表达和验证应用约束。 默认的元数据源是注解,可以通过使用 XML 进行覆盖和扩展。 它不依赖于特定的应用层或编程模型,并且可用于服务器和客户端应用编程。
Hibernate 验证器命令行应用
在下面的示例中,我们在一个简单的命令行应用中使用了 Hibernate Validator。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>HibernateValidation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Maven pom.xml
包含 Hibernate Validator 和 Lombok 库的依赖项。 龙目岛用于减少一些样板。
Car.java
package com.zetcode.bean;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.Data;
@Data
public class Car {
private Long Id;
@NotNull
@Size(min=4, max=50)
private String name;
@Min(value = 1000)
@Max(value = 5000000, message="There is no such expensive car")
private int price;
public Car() {}
public Car(String name, int price) {
this.name = name;
this.price = price;
}
}
我们有一个Car
bean,我们在其中验证数据。
@Data
public class Car {
Car
bean 用 lombok 的@Data
注解修饰。 它会自动创建获取器和设置器方法,equals()
方法,toString()
方法和hashCode()
方法。
@NotNull
@Size(min=4, max=50)
private String name;
@NotNull
注解指出name
属性可能不是null
。 @Size
注解设置属性的最小和最大大小。
@Min(value = 1000)
@Max(value = 5000000, message="There is no such expensive car")
private int price;
@Min
约束设置price
属性的最小值。 message
元素用于创建错误消息。
ClientApp.java
package com.zetcode.client;
import com.zetcode.bean.Car;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class ClientApp {
private static Validator validator;
public static void main(String[] args) {
Car car1 = new Car("Volvo", 29000);
Car car2 = new Car("Skoda", 900);
Car car3 = new Car(null, 29000);
Car car4 = new Car("Cit", 21000);
Car car5 = new Car("Bentley", 8000000);
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
validate(car1);
validate(car2);
validate(car3);
validate(car4);
validate(car5);
}
public static void validate(Car car) {
Set<ConstraintViolation<Car>> cvs = validator.validate(car);
for (ConstraintViolation<Car> cv : cvs) {
System.out.println(cv.getPropertyPath() + ": " + cv.getMessage());
}
}
}
在客户端应用中,我们创建五个汽车对象并对其进行验证。
Car car1 = new Car("Volvo", 29000);
Car car2 = new Car("Skoda", 900);
Car car3 = new Car(null, 29000);
Car car4 = new Car("Cit", 21000);
Car car5 = new Car("Bentley", 8000000);
将创建五个汽车对象。 四辆车的值未通过验证过程。 例如,斯柯达汽车的价格太低; 即低于最小值 1000。
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
validator = factory.getValidator();
验证工厂与buildDefaultValidatorFactory()
方法一起使用。 在工厂,我们使用getValidator()
方法获得了验证器。 建议缓存验证器工厂,因为创建起来很昂贵。
Set<ConstraintViolation<Car>> cvs = validator.validate(car);
使用验证程序的validate()
方法验证汽车。
for (ConstraintViolation<Car> cv : cvs) {
System.out.println(cv.getPropertyPath() + ": " + cv.getMessage());
}
我们将打印错误消息以了解违反约束的情况。
price: must be greater than or equal to 1000
name: may not be null
name: size must be between 4 and 50
price: There is no such expensive car
运行应用时,我们会收到这些错误消息。
Hibernate 验证器 Web 应用
在第二个示例中,我们在 Web 应用中利用了 Hibernate Validator。 该应用部署在 Apache Tomcat 服务器上。 该应用的来源可从作者的 Github 仓库中获得。
图:项目结构
该图显示了 NetBeans 中的项目结构。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/HibernateValidation"/>
context.xml
文件包含应用的上下文路径。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>HibernateValidation2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>HibernateValidation2</name>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator-cdi</artifactId>
<version>5.2.4.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
Maven pom.xml
包含 Java EE Web API,Hibernate Validator 和 Lombok 库的依赖项。
index.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Validation</title>
</head>
<body>
<p>
Enter your name and email:
</p>
<form method="post" action="Greet">
Name: <input type="text" name="username"> <br>
Email: <input type="text" name="email"> <br>
<input type="submit" value="Submit">
</form>
</body>
</html>
index.jsp
是我们应用的入口。 它包含一个带有两个字段的 HTML 表单:用户名和电子邮件。 在这些字段中输入的值将由应用验证。
<form method="post" action="Greet">
...
</form>
提交表单后,将调用Greet
Servlet。
hello.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Greeting</title>
</head>
<body>
Hello <c:out value="${param.username}"/>! <br>
Your email is <c:out value="${param.email}"/>.
</body>
</html>
当输入数据通过验证测试时,将显示hello.jsp
页面。 显示输入的数据。
valError.jsp
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Error</title>
</head>
<body>
<p>
<c:forEach var="err" items="${errMsg}">
<c:out value="${err}"/>
<br>
</c:forEach>
</p>
</body>
</html>
如果验证失败,则显示valError.jsp
。 它显示了存储在errMsg
属性中的错误消息。 该属性是在验证过程中设置的。
User.java
package com.zetcode.bean;
import javax.validation.constraints.Pattern;
import lombok.Data;
import org.hibernate.validator.constraints.NotEmpty;
@Data
public class User {
@NotEmpty
private String name;
@NotEmpty
@Pattern(regexp="^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,5}",
message="Please provide a valid email address")
private String email;
}
User
bean 用 Lombok 和 Hibernate Validator 注解修饰。
@NotEmpty
private String name;
@NotEmpty
注解导致用户名不能为空。
@NotEmpty
@Pattern(regexp="^[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\\.[a-zA-Z.]{2,5}",
message="Please provide a valid email address")
private String email;
电子邮件不能为空,并且必须匹配给定的模式。 用@Pattern
注解设置模式。
DoValidate.java
package com.zetcode.util;
import com.zetcode.bean.User;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
public class DoValidate {
public static List<String> validate(User user) {
List<String> errors = new ArrayList();
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
Set<ConstraintViolation<User>> cvs = validator.validate(user);
if (!cvs.isEmpty()) {
for (ConstraintViolation<User> cv : cvs) {
StringBuilder err = new StringBuilder();
err.append(cv.getPropertyPath());
err.append(" ");
err.append(cv.getMessage());
errors.add(err.toString());
}
}
return errors;
}
}
验证在DoValidate
工具类中执行。
if (!cvs.isEmpty()) {
for (ConstraintViolation<User> cv : cvs) {
StringBuilder err = new StringBuilder();
err.append(cv.getPropertyPath());
err.append(" ");
err.append(cv.getMessage());
errors.add(err.toString());
}
}
当违反约束时,我们将创建错误消息列表。 getMessage()
方法获取约束冲突的错误消息。
return errors;
错误消息列表将返回给调用方。 如果未检测到违规,则该列表为空。
Greeting.java
package com.zetcode.web;
import com.zetcode.bean.User;
import com.zetcode.util.DoValidate;
import java.io.IOException;
import java.util.List;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "Greeting", urlPatterns = {"/Greet"})
public class Greeting extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
String page = "/hello.jsp";
String username = request.getParameter("username");
String email = request.getParameter("email");
User user = new User();
user.setName(username);
user.setEmail(email);
List<String> errors = DoValidate.validate(user);
if (!errors.isEmpty()) {
request.setAttribute("errMsg", errors);
page = "/valError.jsp";
} else {
request.setAttribute("user", user);
}
RequestDispatcher disp = getServletContext().getRequestDispatcher(page);
disp.forward(request, response);
}
}
Greeting
Servlet 检索请求数据并调用DoValidate.validate()
工具方法。 取决于验证的结果,该 servlet 调度到hello.jsp
或valError.jsp
页面。
图:错误消息
如果电子邮件格式不正确,则应用将以错误消息响应。
这是验证过滤器教程。 我们使用 Hibernate Validator,JSTL,JSP,Apache Tomcat 和 Maven 构建了一个控制台和一个 Web 应用。 您可能还需要查看一些相关的教程: Java 验证过滤器教程, Java 教程或 Stripes 教程。
用 Java 显示图像
原文:http://zetcode.com/java/displayimage/
在 Java 中显示图像教程介绍了如何在 Java 中显示图像。 我们展示了如何使用命令行工具 Ant,Maven,NetBeans 和 Eclipse 构建项目。 源代码和图像可在作者的 Github 仓库中获得。
初学者程序员经常在项目中显示图像时遇到问题。 问题在于正确识别图像文件的路径。 关键部分是要认识到图像文件的相对路径是从项目目录开始的。 创建本教程的目的是使事情变得清楚。
以下示例显示了该应用的屏幕截图。
图:用 Java 显示图像
源代码
在这里,我们提供了用于在 Java 中显示图像的源代码。
DisplayImage.java
package com.zetcode;
import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
public class DisplayImage extends JFrame {
public DisplayImage() {
initUI();
}
private void initUI() {
ImageIcon ii = loadImage();
JLabel label = new JLabel(ii);
createLayout(label);
setTitle("Image");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("simg/snake.jpg");
return ii;
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
DisplayImage ex = new DisplayImage();
ex.setVisible(true);
});
}
}
该示例创建一个 Java Swing 应用,并使用ImageIcon
组件显示图像。
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("simg/snake.jpg");
return ii;
}
重要的部分在这里。 ImageIcon
采用图像的文件路径。 该文件路径取决于我们使用的构建工具。
使用命令行工具显示图像
第一个示例使用命令行工具构建 Java 应用。
$ mkdir bin
$ mkdir -p src/main/com/zetcode/
$ mkdir src/maimg
$ cp ~/Pictures/snake.jpg src/maimg/
我们创建项目结构并将图像复制到images
目录。
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("src/maimg/snake.jpg");
return ii;
}
在命令行应用中,我们使用了src/maimg/snake.jpg
路径。
$ tree
.
├── bin
└── src
└── main
├── com
│ └── zetcode
│ └── DisplayImage.java
└── images
└── snake.jpg
6 directories, 2 files
这就是项目目录结构的样子。
$ javac -d bin src/main/com/zetcode/DisplayImage.java
该应用使用javac
工具进行编译。
$ tree
.
├── bin
│ └── com
│ └── zetcode
│ └── DisplayImage.class
└── src
└── main
├── com
│ └── zetcode
│ └── DisplayImage.java
└── images
└── snake.jpg
8 directories, 3 files
编译源代码后,我们在bin/com/zetcode
子目录中创建了一个 Java 类文件。
$ java -cp bin com.zetcode.DisplayImage
我们使用java
命令运行该应用。
使用 Ant 构建项目
在本节中,我们将使用 Ant 构建工具来创建项目。
$ mkdir -p src/main/com/zetcode/
$ mkdir src/maimg
$ cp ~/Pictures/snake.jpg src/maimg/
我们创建目录并复制图像文件。
$ tree
.
├── build.xml
└── src
└── main
├── com
│ └── zetcode
│ └── DisplayImage.java
└── images
└── snake.jpg
5 directories, 3 files
使用tree
命令,显示项目的目录结构。
build.xml
<?xml version="1.0"?>
<project name="DisplayImage" default="compile">
<target name="init">
<mkdir dir="build/classes"/>
</target>
<target name="compile" depends="init">
<javac includeantruntime="false" srcdir="src" destdir="build/classes"/>
</target>
<target name="clean">
<delete dir="build"/>
</target>
</project>
这是 Ant 构建文件。 我们有创建目录,编译源代码和清理的任务。
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("src/maimg/snake.jpg");
return ii;
}
我们使用src/maimg/snake.jpg
路径。
$ ant
Buildfile: /home/janbodnar/prog/javaimages/displayimageant/build.xml
init:
compile:
[javac] Compiling 1 source file to /home/janbodnar/prog/javaimages/displayimageant/build/classes
BUILD SUCCESSFUL
Total time: 2 seconds
我们建立项目。
$ java -cp build/classes/ com.zetcode.DisplayImage
该应用启动。
在 NetBeans 中显示图像
在 NetBeans 中,我们创建一个 Java 应用。
我们创建一个新文件夹。 我们右键单击“源包”,然后选择“新建—文件夹”。
图:在 NetBeans 中创建一个文件夹
我们将文件夹称为images
。 其父目录为src
。 使用拖放操作,将snake.jpg
文件复制到images
子目录。
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("simg/snake.jpg");
return ii;
}
在 NetBeans 中,我们使用了simg/snake.jpg
路径。
System.out.println(System.getProperty("user.dir"));
该应用的当前工作目录是项目目录,在本例中为DisplayImageEx
。 我们可以使用user.dir
系统属性找出当前的工作目录。 src
目录是项目目录的子目录。
图:项目结构 in NetBeans
该图显示了 NetBeans 中的实际项目结构。
在 Eclipse 中显示图像
在 Eclipse 中,我们创建一个 Java 项目。 我们在项目节点上单击鼠标右键,然后选择“新建—源文件夹”。
我们将文件夹名称称为images
。 与 NetBeans 不同,它的父目录是项目文件夹。 使用拖放操作,将snake.jpg
文件复制到images
子目录。
private ImageIcon loadImage() {
ImageIcon ii = new ImageIcon("images/snake.jpg");
return ii;
}
在 Eclipse 中,我们使用了images/snake.jpg
路径。
图:项目结构 in Eclipse
该图显示了 Eclipse 中的实际项目结构。
这是 Java 教程中的图像显示。 我们已经构建了一个 Swing 应用,该应用使用命令行工具,Ant,NetBeans 和 Eclipse 显示图像。 您可能还需要查看ImageIcon
教程,在 Java 中读写 ICO 文件, Java Swing 教程, Java 2D 教程, 或 Java 游戏教程。
Play 框架简介
原文:http://zetcode.com/java/play/
这是 Play 框架的入门教程。 我们使用 Play 创建简单的 Web 应用。 本教程使用 Play 版本 2.5。
Play 是一个用 Scala 和 Java 编写的开源 Web 应用框架。 Play 由 Guillaume Bort 于 2007 年创建。Play 受 ASP.NET MVC,Ruby on Rails 和 Django 的极大启发。 Play 与 Dropwizard,Ninja 框架或 Jodd 等其他框架一起属于下一代 Java Web 框架。
使用模型视图控制器(MVC)架构模式构建播放。 传统的 MVC 模式将应用分为三个部分:模型,视图和控制器。 该模型表示应用中的数据。 视图是数据的视觉表示。 最后,控制器处理并响应事件(通常是用户操作),并可以调用模型上的更改。 这个想法是通过引入一个中间组件:控制器,将数据访问和业务逻辑与数据表示和用户交互分开。
为了优化开发者的生产力,它使用约定而非配置和热代码重载。 优于配置的约定是一种软件设计范式,软件框架使用它来减少设置项目所需的工作。 诸如 Ruby on Rails 或 Play 之类的框架将合理的默认值用于项目结构,将对象属性与表列相关联或为视图命名。 该范式也称为约定编码。
Play 的主要功能
以下是 Play 的主要功能列表:
- Play 默认情况下将 JBoss Netty 用于 Web 服务器。
- 它使用 Scala SBT 构建工具来构建应用。
- 这是一个 RESTful 框架。
- 它具有基于 JPA 的持久层。
- 它使用 Scala 作为模板引擎。
- 它是一个完整的栈框架,其中包含许多常见开发任务的库,例如 JSON 解析,验证,持久性或认证。
Play 放弃了 Java Web 开发中使用的许多传统方法。 它不使用 servlet,也不会将项目打包到 WAR 档案中。 它不使用巨大的整体 Web 应用服务器(JBoss,Glassfish)和 XML 配置文件。
安装 Play
Play 是 Maven 仓库中可用的一系列库。 可以使用任何构建工具来创建 Play 应用。 默认情况下,Play 使用 Sbt 构建工具(默认的 Scala 构建工具)创建 Play 应用。
要创建 Play 应用,我们使用称为激活器的工具。 激活器包括一个 Sbt 构建工具,一组和项目模板以及一个用于管理项目的 Web 界面。 两个重要的种子模板是 Scala 开发者使用的play-scals
和 Java 开发者使用的play-java
。
从 Play 框架的项目页面中,我们下载了激活器。 我们建议下载整个脱机发行版。 下载发行版后,我们将包解压缩到所选目录。 我们将其放入主目录的bin
子目录中。
$ export PATH=$PATH:~/bin/activator-dist-1.3.10/bin/
我们将激活器的bin
目录添加到PATH
变量中。
创建 Play 应用
现在,我们将创建一个新的 Play 应用。
$ activator new first play-java
该命令创建一个名为first
的新 Play Java 应用。
$ cd first
$ tree
.
├── app
│ ├── controllers
│ │ ├── AsyncController.java
│ │ ├── CountController.java
│ │ └── HomeController.java
│ ├── filters
│ │ └── ExampleFilter.java
│ ├── Filters.java
│ ├── Module.java
│ ├── services
│ │ ├── ApplicationTimer.java
│ │ ├── AtomicCounter.java
│ │ └── Counter.java
│ └── views
│ ├── index.scala.html
│ └── main.scala.html
├── bin
│ ├── activator
│ └── activator.bat
├── build.sbt
├── conf
│ ├── application.conf
│ ├── logback.xml
│ └── routes
├── libexec
│ └── activator-launch-1.3.10.jar
├── LICENSE
├── project
│ ├── build.properties
│ └── plugins.sbt
├── public
│ ├── images
│ │ └── favicon.png
│ ├── javascripts
│ │ └── hello.js
│ └── stylesheets
│ └── main.css
├── README
└── test
├── ApplicationTest.java
└── IntegrationTest.java
应用的源代码位于app
目录中。 bin
目录包含激活器工具。 build.sbt
是应用构建脚本。 conf
目录包含配置文件和其他未编译的资源。 project
目录具有 sbt 配置文件。 public
目录包含公共资产,例如静态 HTML 文件,JavaScript 文件,图像或 CSS 文件。 test
目录是用于单元测试或功能测试的源文件夹。 在构建应用时,将生成新目录。 例如,target
目录包含框架生成的文件。
$ activator run
在项目目录中,我们使用activator run
命令运行该应用。 该应用中已经包含一些简单的代码。
图:第一个 Play 应用
默认情况下,应用在端口 9000 上进行监听。 请注意,由于应用是动态编译的,因此存在一些延迟。
现在,我们将对应用进行一些修改。
routes
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
# An example controller showing a sample home page
GET / controllers.HomeController.index
# An example controller showing how to use dependency injection
GET /count controllers.CountController.count
# An example controller showing how to write asynchronous code
GET /message controllers.AsyncController.message
# Map static resources from the /public folder to the /assets URL path
GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset)
在conf/routes
文件中,我们定义了应用的所有路由。 路由器是将每个传入 HTTP 请求转换为操作调用的组件,该操作调用是控制器类中的公共方法。 我们可以看到根路由/
调用了HomeController
的index()
方法。
HomeController.java
package controllers;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.index;
/**
* This controller contains an action to handle HTTP requests
* to the application's home page.
*/
public class HomeController extends Controller {
public Result index() {
return ok(index.render("First application"));
}
}
我们修改HomeController
类。 index()
方法返回ok()
,生成 HTTP 200 OK 结果。 索引操作调用render()
方法,该方法告诉 Play 渲染模板。 使用模板是生成 HTTP 响应的最常见方法。 我们没有明确指定应选择哪个模板,因此 Play 选择默认模板:views/index.scala.html
。
index.scala.html
@(message: String)
@main("Welcome to Play") {
@message
}
我们修改文件以包含上面的代码。 该模板也称为主模板。
main.scala.html
@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
<title>@title</title>
</head>
<body>
@content
</body>
</html>
这是主模板,从索引模板调用。 它用字符串数据填充标题和内容。
图:第一个 Play 应用 2
现在,文档已删除,仅显示该消息。
简单的表单
我们创建一个可以使用 HTML 表单的应用。
$ activator new myform play-java
创建一个新的 Play 应用。
$ tree app
app
├── controllers
│ └── Application.java
├── filters
├── services
└── views
├── greet.scala.html
├── index.scala.html
└── main.scala.html
4 directories, 4 files
我们删除app
子目录中的所有现有文件。 我们创建一个 Java 文件和三个 HTML 文件。
routes
# Routes
GET / controllers.Application.index
GET /greet controllers.Application.greet
在routes
文件中,我们有两个 GET 请求。
Application.java
package controllers;
import play.data.FormFactory;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.index;
import javax.inject.Inject;
public class Application extends Controller {
@Inject
FormFactory formFactory;
public Result index() {
return ok(index.render("Enter your name:"));
}
public Result greet() {
String name = formFactory.form().bindFromRequest().get("name");
StringBuilder sb = new StringBuilder("Hello ");
sb.append(name);
return ok(sb.toString());
}
}
我们有一个Application
控制器。
@Inject
FormFactory formFactory;
我们注入FormFactory
,它是用于创建表单的帮助程序类。
public Result index() {
return ok(index.render("Enter your name:"));
}
index()
操作将呈现一个页面,告诉用户输入其名称。
String name = formFactory.form().bindFromRequest().get("name");
我们将名称请求参数绑定到name
变量。
StringBuilder sb = new StringBuilder("Hello ");
sb.append(name);
return ok(sb.toString());
我们构建并返回一条消息。
index.scala.html
@(message: String)
@main("My form") {
@message
<form action="@routes.Application.greet", method="get">
<input type="text" name="name" />
<button>Submit</button>
</form>
}
在index.scala.html
文件中,我们有一个 HTML 表单,用于发送 GET 请求。
greet.scala.html
@(message: String)
@main("My form") {
@message
}
greet.scala.html
文件显示生成的消息。
main.scala.html
@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
<title>@title</title>
</head>
<body>
@content
</body>
</html>
main.scala.html
文件是主模板文件,它与其他两个模板文件合并。
这是 Play 框架的简介。 您可能还需要查看相关的教程:Stripes 教程, Java MVC 教程, Spark Java 入门, Jtwig 教程, Java 教程或 SQL 查询标签教程。
Java 数据类型
原文:http://zetcode.com/lang/java/datatypes/
在 Java 教程的这一部分中,我们将讨论数据类型。
计算机程序(包括电子表格,文本编辑器,计算器或聊天客户端)可以处理数据。 用于各种数据类型的工具是现代计算机语言的基本组成部分。 数据类型是一组值以及对这些值的允许操作。
Java 编程语言是一种静态类型的语言。 这意味着每个变量和每个表达式都具有在编译时已知的类型。 Java 语言也是一种强类型语言,因为类型会限制变量可以容纳的值或表达式可以产生的值,限制对这些值支持的操作,并确定操作的含义。 强大的静态类型有助于在编译时检测错误。 动态类型语言(例如 Ruby 或 Python)中的变量会随着时间的推移接收不同的数据类型。 在 Java 中,一旦变量声明为某种数据类型,就无法保存其他数据类型的值。
Java 中有两种基本数据类型:基本类型和引用类型。 基本类型为:
boolean
char
byte
short
int
long
float
double
Java 中每种类型都有一个特定的关键字。 基本类型不是 Java 中的对象。 原始数据类型不能存储在仅适用于对象的 Java 集合中。 可以将它们放置在数组中。
引用类型为:
- 类类型
- 接口类型
- 数组类型
还有一个特殊的null
类型,它代表不存在的值。
在 Ruby 编程语言中,一切都是对象。 甚至是基本数据类型。
#!/usr/bin/ruby
4.times { puts "Ruby" }
这个 Ruby 脚本会在控制台上打印四次"Ruby"
字符串。 我们在 4 号上调用次方法。 该数字是 Ruby 中的对象。
Java 有不同的方法。 它具有原始数据类型和包装器类。 包装器类将原始类型转换为对象。 包装器类将在下一章中介绍。
布尔值
我们的世界建立了双重性。 有天与地,水与火,阴与阳,男人与女人,爱与恨。 在 Java 中,boolean
数据类型是具有以下两个值之一的原始数据类型:true
或false
。
快乐的父母正在等待孩子的出生。 他们为两种可能性都选择了名称。 如果要成为男孩,他们选择了罗伯特。 如果要成为女孩,他们会选择维多利亚。
com/zetcode/BooleanType.java
package com.zetcode;
import java.util.Random;
public class BooleanType {
public static void main(String[] args) {
String name = "";
Random r = new Random();
boolean male = r.nextBoolean();
if (male == true) {
name = "Robert";
}
if (male == false) {
name = "Victoria";
}
System.out.format("We will use name %s%n", name);
System.out.println(9 > 8);
}
}
该程序使用随机数生成器来模拟我们的情况。
Random r = new Random();
boolean male = r.nextBoolean();
Random
类用于产生随机数。 nextBoolean()
方法随机返回一个布尔值。
if (male == true) {
name = "Robert";
}
如果布尔变量male
等于true
,则将名称变量设置为"Robert"
。 if
关键字适用于布尔值。
if (male == false) {
name = "Victoria";
}
如果随机生成器选择false
,则将名称变量设置为"Victoria"
。
System.out.println(9 > 8);
关系运算符导致布尔值。 此行在控制台上显示为true
。
$ java BooleanType.java
We will use name Robert
true
$ java BooleanType.java
We will use name Robert
true
$ java BooleanType.java
We will use name Victoria
true
多次运行该程序。
整数
整数是实数的子集。 它们写时没有小数或小数部分。 整数落入集合Z = {..., -2, -1, 0, 1, 2, ......}
中整数是无限的。
在计算机语言中,整数(通常)是原始数据类型。 实际上,计算机只能使用整数值的子集,因为计算机的容量有限。 整数用于计算离散实体。 我们可以有 3、4 或 6 个人,但不能有 3.33 个人。 我们可以拥有 3.33 公斤,4.564 天或 0.4532 公里。
类型 | 大小 | 范围 |
---|---|---|
byte |
8 位 | -128 至 127 |
short |
16 位 | -32,768 至 32,767 |
char |
16 位 | 0 至 65,535 |
int |
32 位 | -2,147,483,648 至 2,147,483,647 |
long |
64 位 | -9,223,372,036,854,775,808 至 9,223,372,036,854,775,807 |
Table: Integer types in Java
可以根据我们的需要使用这些整数类型。 然后,我们可以将byte
类型用于存储妇女生育的孩子数量的变量。 经过验证的年龄最大的人死于 122 岁,因此我们可能至少选择short
类型作为年龄变量。 这将为我们节省一些内存。
整数字面值可以用十进制,十六进制,八进制或二进制表示法表示。 如果数字后缀为 ASCII 字母L
或l
,则其类型为long
。 否则为int
类型。 大写字母L
首选用于指定长数字,因为小写字母l
容易与数字 1 混淆。
int a = 34;
byte b = 120;
short c = 32000;
long d = 45000;
long e = 320000L;
我们有五个赋值。 值 34、120、32000 和 45000 是int
类型的整数字面值。 byte
和short
类型没有整数字面值。 如果这些值适合目标类型,则编译器不会抱怨并自动执行转换。 对于小于Integer.MAX_VALUE
的long
数字,L
后缀是可选的。
long x = 2147483648L;
long y = 2147483649L;
对于大于Integer.MAX_VALUE
的long
数字,我们必须添加L
后缀。
当我们使用整数时,我们处理离散项。 例如,我们可以使用整数来计算苹果。
com/zetcode/Apples.java
package com.zetcode;
public class Apples {
public static void main(String[] args) {
int baskets = 16;
int applesInBasket = 24;
int total = baskets * applesInBasket;
System.out.format("There are total of %d apples%n", total);
}
}
在我们的程序中,我们计算了苹果的总量。 我们使用乘法运算。
int baskets = 16;
int applesInBasket = 24;
篮子数和每个篮子中的苹果数是整数值。
int total = baskets * applesInBasket;
将这些值相乘,我们也得到一个整数。
$ java Apples.java
There are total of 384 apples
这是程序的输出。
可以在 Java 中使用四种不同的表示法指定整数:十进制,八进制,十六进制和二进制。 二进制符号是在 Java 7 中引入的。众所周知,通常使用小数。 八进制数字以0
字符开头,后跟八进制数字。 十六进制数字以0x
字符开头,后跟十六进制数字。 二进制数字以0b
开头,后跟二进制数字(零和一)。
com/zetcode/IntegerNotations.java
package com.zetcode;
public class IntegerNotations {
public static void main(String[] args) {
int n1 = 31;
int n2 = 0x31;
int n3 = 031;
int n4 = 0b1001;
System.out.println(n1);
System.out.println(n2);
System.out.println(n3);
System.out.println(n4);
}
}
我们有四个整数变量。 每个变量都被分配了一个具有不同整数符号的值。
int n1 = 31;
int n2 = 0x31;
int n3 = 031;
int n4 = 0b1001;
第一个是十进制,第二个十六进制,第三个八进制和第四个二进制。
$ java IntegerNotations.java
31
49
25
9
我们看到了程序的输出。
大数字很难阅读。 如果我们有一个像 245342395423452 这样的数字,我们会发现很难快速阅读它。 在计算机外部,大数字之间用空格或逗号分隔。 从 Java SE 1.7 开始,可以用下划线分隔整数。
下划线不能在数字的开头或结尾,浮点字面量中的小数点附近以及F
或L
后缀之前使用。
com/zetcode/UsingUnderscores.java
package com.zetcode;
public class UsingUnderscores {
public static void main(String[] args) {
long a = 23482345629L;
long b = 23_482_345_629L;
System.out.println(a == b);
}
}
此代码示例演示了 Java 中下划线的用法。
long a = 23482345629L;
long b = 23_482_345_629L;
我们有两个相同的长数字。 在第二个数字中,我们将数字中的每三个数字分开。 比较这两个数字,我们得到一个布尔值true
。 L
后缀告诉编译器我们有一个长整数。
使用的 Java byte
,short
,int
和long
类型确实表示固定精度数字。 这意味着它们可以表示有限数量的整数。 长类型可以代表的最大整数是 9223372036854775807。如果要处理更大的数字,则必须使用java.math.BigInteger
类。 它用于表示不可变的任意精度整数。 任意精度整数仅受可用计算机内存量的限制。
com/zetcode/VeryLargeIntegers.java
package com.zetcode;
import java.math.BigInteger;
public class VeryLargeIntegers {
public static void main(String[] args) {
System.out.println(Long.MAX_VALUE);
BigInteger b = new BigInteger("92233720368547758071");
BigInteger c = new BigInteger("52498235605326345645");
BigInteger a = b.multiply(c);
System.out.println(a);
}
}
在java.math.BigInteger
类的帮助下,我们将两个非常大的数字相乘。
System.out.println(Long.MAX_VALUE);
我们打印可以用long
类型表示的最大整数值。
BigInteger b = new BigInteger("92233720368547758071");
BigInteger c = new BigInteger("52498235605326345645");
我们定义了两个BigInteger
对象。 它们都具有long
类型可以容纳的更大的值。
BigInteger a = b.multiply(c);
使用multiply()
方法,我们将两个数字相乘。 请注意,BigInteger
数字是不可变的。 该操作返回一个新值,我们将其分配给新变量。
System.out.println(a);
计算出的整数将打印到控制台。
$ java VeryLargeIntegers.java
9223372036854775807
4842107582663807707870321673775984450795
这是示例输出。
算术溢出
算术溢出是在计算产生的结果的大小大于给定寄存器或存储位置可以存储或表示的结果时发生的条件。
com/zetcode/Overflow.java
package com.zetcode;
public class Overflow {
public static void main(String[] args) {
byte a = 126;
System.out.println(a);
a++;
System.out.println(a);
a++;
System.out.println(a);
a++;
System.out.println(a);
}
}
在此示例中,我们尝试分配一个超出数据类型范围的值。 这导致算术溢出。
$ java Overflow.java
126
127
-128
-127
发生溢出时,变量将重置为负的上限值。
浮点数字
实数测量连续的数量,例如重量,高度或速度。 浮点数表示计算中实数的近似值。 在 Java 中,我们有两种原始浮点类型:float
和double
。 float
是单精度类型,以 32 位存储数字。 double
是双精度类型,以 64 位存储数字。 这两种类型具有固定的精度,不能完全表示所有实数。 在必须使用精确数字进行处理的情况下,可以使用BigDecimal
类。
带有F/f
后缀的浮点数是float
类型,double
数字有D/d
后缀。 double
数字的后缀是可选的。
假设一个短跑运动员跑了 1 个小时,跑了 9.87 秒。 他的公里/小时速度是多少?
com/zetcode/Sprinter.java
package com.zetcode;
public class Sprinter {
public static void main(String[] args) {
float distance;
float time;
float speed;
distance = 0.1f;
time = 9.87f / 3600;
speed = distance / time;
System.out.format("The average speed of a sprinter is %f km/h%n", speed);
}
}
在此示例中,必须使用浮点值。 在这种情况下,浮点数据类型的低精度不会造成问题。
distance = 0.1f;
100m 是 0.1km。
time = 9.87f / 3600;
9.87s 是9.87 / 60 * 60 h
。
speed = distance / time;
为了获得速度,我们将距离除以时间。
$ java Sprinter.java
The average speed of a sprinter is 36.474163 km/h
这是程序的输出。 小数舍入误差不会影响我们对短跑运动员速度的理解。
float
和double
类型不精确。
com/zetcode/FloatingInPrecision.java
package com.zetcode;
public class FloatingInPrecision {
public static void main(String[] args) {
double a = 0.1 + 0.1 + 0.1;
double b = 0.3;
System.out.println(a);
System.out.println(b);
System.out.println(a == b);
}
}
该代码示例说明了浮点值的不精确性质。
double a = 0.1 + 0.1 + 0.1;
double b = 0.3;
我们定义两个double
值。 D/d
后缀是可选的。 乍一看,它们应该相等。
System.out.println(a);
System.out.println(b);
打印它们将显示很小的差异。
System.out.println(a == b);
该行将返回false
。
$ java FloatingInPrecision.java
0.30000000000000004
0.3
false
边距误差很小。 因此,比较运算符返回布尔值false
。
当我们使用货币,货币以及通常用于业务应用时,我们需要使用精确的数字。 基本浮点类型的舍入误差是不可接受的。
com/zetcode/CountingMoney.java
package com.zetcode;
public class CountingMoney {
public static void main(String[] args) {
float c = 1.46f;
float sum = 0f;
for (int i=0; i<100_000; i++) {
sum += c;
}
System.out.println(sum);
}
}
1.46f
代表 1 欧元和 46 美分。 我们从 100000 个这样的金额中创建一个总和。
for (int i=0; i<100_000; i++) {
sum += c;
}
在此循环中,我们从 100000 这样的金额中创建一个总和。
$ java CountingMoney.java
146002.55
计算得出的错误为 2 欧元和 55 美分。
为了避免此裕度误差,我们利用了BigDecimal
类。 它用于保存不可变的,任意精度的带符号十进制数字。
com/zetcode/CountingMoney2.java
package com.zetcode;
import java.math.BigDecimal;
public class CountingMoney2 {
public static void main(String[] args) {
BigDecimal c = new BigDecimal("1.46");
BigDecimal sum = new BigDecimal("0");
for (int i=0; i<100_000; i++) {
sum = sum.add(c);
}
System.out.println(sum);
}
}
我们用相同的金额执行相同的操作。
BigDecimal c = new BigDecimal("1.46");
BigDecimal sum = new BigDecimal("0");
我们定义两个BigDecimal
数字。
for (int i=0; i<100_000; i++) {
sum = sum.add(c);
}
BigDecimal
数字是不可变的,因此在每个循环中始终将一个新对象分配给sum
变量。
$ java CountingMoney2.java
146000.00
在此示例中,我们获得了精确值。
Java 支持浮点值的科学语法。 也称为指数表示法,它是一种写数字太大或太小而不能方便地用标准十进制表示法写的方式。
com/zetcode/ScientificNotation.java
package com.zetcode;
import java.math.BigDecimal;
import java.text.DecimalFormat;
public class ScientificNotation {
public static void main(String[] args) {
double n = 1.235E10;
DecimalFormat dec = new DecimalFormat("#.00");
System.out.println(dec.format(n));
BigDecimal bd = new BigDecimal("1.212e-19");
System.out.println(bd.toEngineeringString());
System.out.println(bd.toPlainString());
}
}
我们使用科学计数法定义两个浮点值。
double n = 1.235E10;
这是double
类型的浮点值,以科学计数法表示。
DecimalFormat dec = new DecimalFormat("#.00");
System.out.println(dec.format(n));
我们使用DecimalFormat
类将double
值排列为标准十进制格式。
BigDecimal bd = new BigDecimal("1.212e-19");
System.out.println(bd.toEngineeringString());
System.out.println(bd.toPlainString());
BigDecimal
类采用科学计数法中的浮点值作为参数。 我们使用该类的两种方法来打印工程字符串和纯字符串中的值。
$ java ScientificNotation.java
12350000000.00
121.2E-21
0.0000000000000000001212
This is the example output.
枚举
枚举类型是一种特殊的数据类型,它使变量成为一组预定义的常量。 可以将任何枚举器分配为已声明为具有枚举类型的变量作为值。 枚举使代码更具可读性。 当我们处理只能从一小部分可能的值中取出一个的变量时,枚举很有用。
com/zetcode/Enumerations.java
package com.zetcode;
public class Enumerations {
enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
public static void main(String[] args) {
Days day = Days.MONDAY;
if (day == Days.MONDAY) {
System.out.println("It is Monday");
}
System.out.println(day);
for (Days d : Days.values()) {
System.out.println(d);
}
}
}
在我们的代码示例中,我们为工作日创建一个枚举。
enum Days {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
使用enum
关键字创建代表星期几的枚举。 枚举项是常量。 按照约定,常量用大写字母表示。
Days day = Days.MONDAY;
我们有一个名为day
的变量,其类型为Days
。 它被初始化为星期一。
if (day == Days.MONDAY) {
System.out.println("It is Monday");
}
与将日变量与某个数字进行比较相比,此代码更具可读性。
System.out.println(day);
该行将在星期一打印到控制台。
for (Days d : Days.values()) {
System.out.println(d);
}
此循环将整天打印到控制台。 静态values()
方法按声明顺序返回包含此enum
类型常量的数组。 此方法可用于通过增强的for
语句迭代常量。 增强的for
逐元素遍历数组,并将其打印到终端。
$ java Enumerations.java
It is Monday
MONDAY
MONDAY
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY
This is the example output.
可以给枚举常量一些值。
com/zetcode/Enumerations2.java
package com.zetcode;
enum Season {
SPRING(10),
SUMMER(20),
AUTUMN(30),
WINTER(40);
private int value;
private Season(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class Enumerations2 {
public static void main(String[] args) {
for (Season season : Season.values()) {
System.out.println(season + " " + season.getValue());
}
}
}
该示例包含一个Season
枚举,该枚举具有四个常量。
SPRING(10),
SUMMER(20),
AUTUMN(30),
WINTER(40);
在这里,我们定义enum
的四个常数。 给常数指定特定值。
private int value;
private Season(int value) {
this.value = value;
}
当定义常量时,我们还必须创建一个构造器。 构造器将在本教程的后面部分介绍。
SPRING 10
SUMMER 20
AUTUMN 30
WINTER 40
This is the example output.
字符串和字符
String
是代表计算机程序中文本数据的数据类型。 Java 中的字符串是字符序列。 char
是单个字符。 字符串用双引号引起来。
由于字符串在每种编程语言中都非常重要,因此我们将为它们专门整整一章。 在这里,我们仅举一个小例子。
com/zetcode/StringsChars.java
package com.zetcode;
public class StringsChars {
public static void main(String[] args) {
String word = "ZetCode";
char c = word.charAt(0);
char d = word.charAt(3);
System.out.println(c);
System.out.println(d);
}
}
程序将Z
字符打印到终端。
String word = "ZetCode";
在这里,我们创建一个字符串变量,并为其分配"ZetCode"
值。
char c = word.charAt(0);
charAt()
方法返回指定索引处的char
值。 序列的第一个char
值在索引 0 处,第二个在索引 1 处,依此类推。
$ java StringsChars.java
Z
C
该程序将"ZetCode"
字符串的第一个和第四个字符打印到控制台。
数组
数组是处理元素集合的复杂数据类型。 每个元素都可以通过索引访问。 数组的所有元素必须具有相同的数据类型。
我们将整章专门介绍数组。 这里我们仅显示一个小例子。
com/zetcode/ArraysEx.java
package com.zetcode;
public class ArraysEx {
public static void main(String[] args) {
int[] numbers = new int[5];
numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;
int len = numbers.length;
for (int i = 0; i < len; i++) {
System.out.println(numbers[i]);
}
}
}
在此示例中,我们声明一个数组,用数据填充它,然后将数组的内容打印到控制台。
int[] numbers = new int[5];
我们创建一个整数数组,该数组最多可以存储 5 个整数。 因此,我们有五个元素组成的数组,索引为0..4
。
numbers[0] = 3;
numbers[1] = 2;
numbers[2] = 1;
numbers[3] = 5;
numbers[4] = 6;
在这里,我们为创建的数组分配值。 我们可以通过数组访问符号访问数组的元素。 它由数组名称和方括号组成。 在方括号内,我们指定所需元素的索引。
int len = numbers.length;
每个数组都有一个length
属性,该属性返回数组中的元素数。
for (int i = 0; i < len; i++) {
System.out.println(numbers[i]);
}
我们遍历数组并将数据打印到控制台。
$ java ArraysEx.java
3
2
1
5
6
This is the output of the program.
在 Java 教程的这一部分中,我们介绍了 Java 中的数据类型。
Spark Java 简介
原文:http://zetcode.com/java/spark/
这是 Spark Java Web 框架的入门教程。 我们介绍了 Spark Java 框架,并提供了三个代码示例。
Spark Java
Spark 是一个 Java 微框架,用于以最小的工作量在 Java8 中创建 Web 应用。 Spark 框架是为快速开发而构建的简单,轻量级的 Java Web 框架。 它的灵感来自流行的 Ruby 微框架 Sinatra 。
Spark 广泛使用 Java8 的 lambda 表达式,这使 Spark 应用不再那么冗长。 与其他 Java Web 框架相比,Spark 不使用大量的 XML 文件或注解。
路由
Spark 应用包含一组路由。 路由将 URL 模式映射到 Java 处理器。
路由包含三个部分:
- 动词,包括获取,发布,放置,删除,开头,跟踪,连接和选项
- 诸如
/first
或/hello/:name
之类的路径 - 回调
(request, response) -> {}
首个应用
第一个应用返回一条简单消息。 Gradle 用于构建应用。
$ tree
.
├── build.gradle
└── src
└── main
└── java
└── com
└── zetcode
└── firstspark
└── FirstSpark.java
这是项目结构。 Gradle 的 Java 插件期望 Java 生产代码位于src/main/java
目录中。
build.gradle
apply plugin: 'java'
apply plugin: 'application'
archivesBaseName = "first"
version = '1.0'
mainClassName = "com.zetcode.firstspark.FirstSpark"
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.5'
compile 'org.slf4j:slf4j-simple:1.7.6'
}
这是 Gradle 构建文件。 它包括 Spark 核心组件和 slf4j 简单记录器的依赖项。
FirstSpark.java
package com.zetcode.firstspark;
import static spark.Spark.get;
public class FirstSpark {
public static void main(String[] args) {
get("/first", (req, res) -> "First Spark application");
}
}
应用将"First Spark application"
返回到 GET 请求。 当我们运行应用时,Spark 将启动嵌入式 Jetty Web 服务器。
get("/first", (req, res) -> "First Spark application");
get()
方法映射 HTTP GET 请求的路由。 在 Spark 术语中,路由是处理器。路由是映射到处理器的 URL 模式。 处理器可以是物理文件,也可以是
$ gradle build
我们使用gradle build
命令构建应用。
$ gradle run
我们使用gradle run
命令运行该应用。 嵌入式 Jetty 服务器启动。
$ curl localhost:4567/first
First Spark application
我们使用curl
工具将 GET 请求发送到服务器。 内置嵌入式 Jetty 服务器默认情况下监听端口 4567。
你好应用
第二个应用将向用户打招呼。 客户端发送带有 URL 的名称,应用向用户打招呼。
build.gradle
apply plugin: 'java'
apply plugin: 'application'
archivesBaseName = "hello"
version = '1.0'
mainClassName = "com.zetcode.hellospark.HelloSpark"
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.5'
compile 'org.slf4j:slf4j-simple:1.7.6'
}
这是应用的 Gradle 构建文件。
$ tree
.
├── build.gradle
└── src
└── main
└── java
└── com
└── zetcode
└── hellospark
└── HelloSpark.java
6 directories, 2 files
这是项目结构。
HelloSpark.java
package com.zetcode.hellospark;
import static spark.Spark.get;
public class HelloSpark {
public static void main(String[] args) {
get("/hello/:name", (req, res) -> "Hello " + req.params(":name"));
}
}
Spark 应用检索请求参数,生成一条消息,然后将其返回给调用方。
get("/hello/:name", (req, res) -> "Hello " + req.params(":name"));
params()
方法返回提供的路由模式参数的值。
$ gradle build run
我们构建并运行该应用。
$ curl localhost:4567/hello/Peter
Hello Peter
我们向服务器发送请求; URL 包含一个名称。 该应用发回问候。
在 Tomcat 中运行 Spark 应用
默认情况下,Spark 应用在嵌入式 Jetty 服务器中运行。 在此示例中,我们显示了如何在 Tomcat 中运行 Spark Java 应用。 这次我们使用 Maven 构建工具并在 NetBeans 中创建一个项目。
图:NetBeans 项目结构
该图显示了该项目在 NetBeans 中的外观。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>HelloSpark2</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>HelloSpark2</name>
<properties>
<endorsed.dir>${project.build.directory}/endorsed</endorsed.dir>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<compilerArguments>
<endorseddirs>${endorsed.dir}</endorseddirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
这是 Maven 构建文件。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/HelloSpark2"/>
这是context.xml
文件。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<filter>
<filter-name>SparkFilter</filter-name>
<filter-class>spark.servlet.SparkFilter</filter-class>
<init-param>
<param-name>applicationClass</param-name>
<param-value>com.zetcode.hellospark2.HelloSpark</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>SparkFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在web.xml
部署描述符中,我们指定spark.servlet.SparkFilter
。
HelloSpark.java
package com.zetcode.hellospark2;
import static spark.Spark.get;
import spark.servlet.SparkApplication;
public class HelloSpark implements SparkApplication {
@Override
public void init() {
get("/hello/:name", (request, response) -> "Hello " + request.params(":name"));
}
}
我们实现SparkApplication
接口,并在init()
方法中指定路由。
最后,我们运行 Tomcat Web 服务器。
$ curl localhost:8084/HelloSpark2/hello/Peter
Hello Peter
NetBeans 的内置 Tomcat 监听端口 8084。
模板引擎
Spark 没有自己的模板系统。 它使用第三方引擎。 在以下两个示例中,我们使用 Thymeleaf 和 FreeMarker。
使用 Thymeleaf
在以下示例中,我们将把 Thymeleaf 模板引擎集成到我们的 Spark 应用中。 Thymeleaf 是适用于 Web 和独立环境的现代服务器端 Java 模板引擎。
$ tree
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── zetcode
│ └── thymeleaf
│ └── SparkThymeleaf.java
└── resources
└── templates
└── hello.html
这是项目的目录结构。 模板文件位于src/main/resources/templates
目录中。
build.gradle
apply plugin: 'java'
apply plugin: 'application'
archivesBaseName = "sparkthymeleaf"
version = '1.0'
mainClassName = "com.zetcode.thymeleaf.SparkThymeleaf"
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.5'
compile 'org.slf4j:slf4j-simple:1.7.6'
compile 'com.sparkjava:spark-template-thymeleaf:2.3'
}
在这里,我们有 Gradle 构建文件,其中包含spark-template-thymeleaf
依赖项。
SparkThymeleaf.java
package com.zetcode.thymeleaf;
import java.util.HashMap;
import java.util.Map;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import spark.template.thymeleaf.ThymeleafTemplateEngine;
import static spark.Spark.get;
import static spark.Spark.staticFileLocation;
public class SparkThymeleaf {
public static void main(String[] args) {
get("/hello/:name", SparkThymeleaf::message, new ThymeleafTemplateEngine());
}
public static ModelAndView message(Request req, Response res) {
Map<String, Object> params = new HashMap<>();
params.put("name", req.params(":name"));
return new ModelAndView(params, "hello");
}
}
应用读取请求参数并将其放入ModelAndView
对象。
get("/hello/:name", SparkThymeleaf::message, new ThymeleafTemplateEngine());
ThymeleafTemplateEngine
的实例传递给get()
方法。
hello.html
<pre class="code">
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Hello user</title>
</head>
<body>
<p th:inline="text">Hello, [[${name}]]!</p>
</body>
</html>
这是hello.html
模板文件。 它引用随ModelAndView
对象传递的名称变量。
$ curl localhost:4567/hello/Peter
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8" />
<title>Hello user</title>
</head>
<body>
<p>Hello, Peter!</p>
</body>
</html>
我们得到这个输出。
FreeMarker
在以下示例中,我们将把 FreeMarker 模板引擎集成到我们的 Spark 应用中。 FreeMarker 是一个完善的 Java 模板引擎。
$ tree
.
├── build.gradle
└── src
└── main
├── java
│ └── com
│ └── zetcode
│ └── SparkFreeMarker.java
└── resources
└── views
└── hello.ftl
这是项目的目录结构。 模板文件位于src/main/resources/views
目录中。
build.gradle
apply plugin: 'application'
sourceCompatibility = '1.8'
version = '1.0'
mainClassName = "com.zetcode.SparkFreeMarker"
repositories {
mavenCentral()
}
dependencies {
compile 'com.sparkjava:spark-core:2.5.5'
compile 'org.slf4j:slf4j-simple:1.7.24'
compile 'com.sparkjava:spark-template-freemarker:2.5.5'
}
在这里,我们有 Gradle 构建文件,其中包含spark-template-freemarker
依赖项。
SparkFreeMarker.java
package com.zetcode;
import freemarker.template.Configuration;
import freemarker.template.Version;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import static spark.Spark.get;
import spark.template.freemarker.FreeMarkerEngine;
public class SparkFreeMarker {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration(new Version(2, 3, 23));
conf.setClassForTemplateLoading(SparkFreeMarker.class, "/views");
get("/hello/:name", SparkFreeMarker::message, new FreeMarkerEngine(conf));
}
public static ModelAndView message(Request req, Response res) {
Map<String, Object> params = new HashMap<>();
params.put("name", req.params(":name"));
return new ModelAndView(params, "hello.ftl");
}
}
我们为 FreeMarker 设置了相同的应用。
Configuration conf = new Configuration(new Version(2, 3, 23));
conf.setClassForTemplateLoading(SparkFreeMarker.class, "/views");
我们用Configuration
类配置 FreeMarker。 模板文件将放置在views
目录中,该目录必须位于类路径上。
get("/hello/:name", SparkFreeMarker::message, new FreeMarkerEngine(conf));
FreeMarkerEngine
传递给get()
方法。
hello.ftl
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Hello ${name}</p>
</body>
</html>
这是hello.ftl
模板文件; 它引用随ModelAndView
对象传递的名称变量。
$ curl localhost:4567/hello/Lucy
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Hello Lucy</p>
</body>
</html>
这是输出。
在本教程中,我们介绍了 Spark Java 框架。 您可能也对相关教程感兴趣: Java 教程,游戏介绍,Stripes 介绍或 Jtwig 教程。
Java ResourceBundle
教程
原文:http://zetcode.com/java/resourcebundle/
在 Java ResourceBundle
教程中,我们展示了如何在 Java 中使用ResourceBundle
。
硬编码特定于语言环境的数据不是要走的路。 诸如消息或标签之类的值应放在单独的文件中。 这样,我们可以处理多个语言环境,而不必为每个语言环境编写不同的代码。 对于翻译人员来说,这也很方便,因为它们仅处理可翻译的文本,而不查看编程代码。
Java ResourceBundle
资源包是 Java 属性文件,其中包含特定于语言环境的数据。 通过使代码与语言环境无关,这是使 Java 应用国际化的一种方式。
资源包捆绑到具有相同基本名称的族中。 例如,如果我们有一个words
基本名称,则words_sk
与斯洛伐克语的语言环境匹配。 如果不支持特定的语言环境,则使用默认资源束。
资源包还支持方言。 例如words_es_AR
用于阿根廷使用的西班牙语,而玻利维亚使用words_es_BO
。
ResourceBundle
是一个抽象类,具有两个子类:PropertyResourceBundle
和ListResourceBundle
。 PropertyResourceBundle
从属性文件加载数据。 属性文件是包含可翻译文本的纯文本文件。 属性文件不是 Java 源代码的一部分,它们只能包含String
值。 ListResourceBundle
通过方便的列表管理资源; 它从类文件中获取数据。 我们可以将任何特定于语言环境的对象存储在ListResourceBundle
中。
为了获得适当的ResourceBundle
,我们调用ResourceBundle.getBundle()
方法。 这是一种寻找ListResourceBundle
的工厂方法,如果找不到,将寻找PropertyResourceBundle
。 如果找不到资源束,则抛出MissingResourceException
。
Java PropertyResourceBundle
示例
在第一个应用中,我们创建一个简单的 Java 应用,该应用使用三个资源包:默认的英语,德语和斯洛伐克语。
图:NetBeans 项目结构
我们创建三个属性文件,并将它们放置在resources
目录中。
words.properties
w1 = Earth
w2 = ocean
这是默认属性文件。 它通常是英语。 文件中有两个词。
words_de.properties
w1 = Erde
w2 = ozean
words_de.properties
文件包含德语单词。
words_sk.properties
w1 = Zem
w2 = oceán
words_de.properties
文件包含斯洛伐克语单词。
ResourceBundleEx.java
package com.zetcode;
import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBundleEx {
static public void main(String[] args) {
Locale[] locales = {
Locale.GERMAN,
new Locale("sk", "SK"),
Locale.ENGLISH
};
System.out.println("w1:");
for (Locale locale : locales) {
getWord(locale, "w1");
}
System.out.println("w2:");
for (Locale locale : locales) {
getWord(locale, "w2");
}
}
static void getWord(Locale curLoc, String key) {
ResourceBundle words
= ResourceBundle.getBundle("resources/words", curLoc);
String value = words.getString(key);
System.out.printf("Locale: %s, Value: %s %n", curLoc.toString(), value);
}
}
在代码示例中,我们打印了三个资源包中使用的所有单词。
Locale[] locales = {
Locale.GERMAN,
new Locale("sk", "SK"),
Locale.ENGLISH
};
在示例中,我们有三种语言环境:德语,斯洛伐克语和英语。
for (Locale locale : locales) {
getWord(locale, "w1");
}
我们遍历语言环境并打印带有w1
键标记的单词。
ResourceBundle words
= ResourceBundle.getBundle("resources/words", curLoc);
使用ResourceBundle.getBundle()
方法,可以获得当前使用的语言环境的捆绑软件。 由于我们尚未创建ListResourceBundle
,因此该方法使用PropertyResourceBundle
,从而从属性文件加载数据。
String value = words.getString(key);
System.out.printf("Locale: %s, Value: %s %n", curLoc.toString(), value);
我们获取值并打印语言环境名称,键和值。
w1:
Locale: de, Value: Erde
Locale: sk_SK, Value: Zem
Locale: en, Value: Earth
w2:
Locale: de, Value: ozean
Locale: sk_SK, Value: oceán
Locale: en, Value: ocean
这是示例的输出。
Java ListResourceBundle
示例
在以下应用中,我们使用ListResourceBundle
。
图:NetBeans 项目结构 II
我们为斯洛伐克语和捷克语创建语言环境资源。
MyResources_sk.java
package com.zetcode.myres;
import java.util.ListResourceBundle;
public class MyResources_sk extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return resources;
}
private final Object[][] resources = {
{ "Capital", "Bratislava" },
{ "Area", 49035 },
{ "Currency", "EUR" },
};
}
在这里,我们为斯洛伐克语实现了ListResourceBundle
的实现。 我们必须重写getContents()
方法。 该方法返回键/值对的数组。
MyResources_cs_CZ.java
package com.zetcode.myres;
import java.util.ListResourceBundle;
public class MyResources_cs_CZ extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return resources;
}
private final Object[][] resources = {
{ "Capital", "Praha" },
{ "Area", 78866 },
{ "Currency", "CZK" },
};
}
这是捷克语的实现。
ResourceBundleEx2.java
package com.zetcode;
import java.util.Locale;
import java.util.ResourceBundle;
public class ResourceBundleEx2 {
public static void main(String[] args) {
Locale sk_loc = new Locale("sk", "SK");
ResourceBundle bundle =
ResourceBundle.getBundle("com.zetcode.myres.MyResources", sk_loc);
System.out.println("Capital: " + bundle.getObject("Capital"));
System.out.println("Area: " + bundle.getObject("Area"));
System.out.println("Currency: " + bundle.getObject("Currency"));
System.out.println();
Locale cz_loc = new Locale("cs", "CZ");
ResourceBundle bundle2 =
ResourceBundle.getBundle("com.zetcode.myres.MyResources", cz_loc);
System.out.println("Capital: " + bundle2.getObject("Capital"));
System.out.println("Area: " + bundle2.getObject("Area"));
System.out.println("Currency: " + bundle2.getObject("Currency"));
}
}
该示例打印了斯洛伐克和捷克共和国的一些地理数据。
Locale sk_loc = new Locale("sk", "SK");
ResourceBundle bundle =
ResourceBundle.getBundle("com.zetcode.myres.MyResources", sk_loc);
使用ResourceBundle.getBundle()
方法,我们从com.zetcode.myres.MyResources_sk.class
创建资源束。
Capital: Bratislava
Area: 49035
Currency: EUR
Capital: Praha
Area: 78866
Currency: CZK
This is the output of the example.
Swing 应用
在第三个示例中,我们使用 Java Swing 创建了一个简单的 GUI 应用。 该示例可以动态更改 UI 的语言。 该示例使用ListResourceBundle
类。 对于不熟悉 Swing 的人,ZetCode 上有一个 Java Swing 教程。
源代码和图像可在作者的 Github 仓库中获得。
MyResources_sk.java
package com.zetcode.myres;
import java.util.ListResourceBundle;
import javax.swing.ImageIcon;
public class MyResources_sk extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return resources;
}
private final Object[][] resources = {
{"name", "Slovensko"},
{"lang_menu", "Jazyk"},
{"lang_sk", "Slovenčina"},
{"lang_hu", "Maďarčina"},
{"flag", new ImageIcon("src/resources/slovakia.png")},
{"description", "Slovensko je vnútrozemský štát v strednej Európe."}
};
}
这些是斯洛伐克语的资源。 我们有五个字符串和一个ImageIcon
。
MyResources_hu.java
package com.zetcode.myres;
import java.util.ListResourceBundle;
import javax.swing.ImageIcon;
public class MyResources_hu extends ListResourceBundle {
@Override
protected Object[][] getContents() {
return resources;
}
private final Object[][] resources = {
{"name", "Magyarország"},
{"lang_menu", "Nyelv"},
{"lang_sk", "Szlovák"},
{"lang_hu", "Magyar"},
{"flag", new ImageIcon("src/resources/hungary.png")},
{"description", "Magyarország közép-európai ország "
+ "a Kárpát-medencében."}
};
}
这些是匈牙利语的资源。
ResourceBundleEx3.java
package com.zetcode;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.ButtonGroup;
import javax.swing.GroupLayout;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.LayoutStyle;
/*
* Java ResourceBundle tutorial
*
* This program uses a ResourceBundle in a
* Java Swing application.
*
* Author: Jan Bodnar
* Website: zetcode.com
* Last modified: August 2016
*/
public class ResourceBundleEx3 extends JFrame {
private ResourceBundle bundle;
private JLabel flag;
private JLabel lbl;
private JMenu langMenu;
private JRadioButtonMenuItem skMenuItem;
private JRadioButtonMenuItem huMenuItem;
public ResourceBundleEx3() {
initUI();
}
private void initUI() {
createMenuBar();
flag = new JLabel();
lbl = new JLabel();
updateLanguage(new Locale("sk", "SK"));
createLayout(lbl, flag);
pack();
setTitle(bundle.getString("name"));
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void updateLanguage(Locale locale) {
bundle = ResourceBundle.getBundle("com.zetcode.myres.MyResources", locale);
langMenu.setText(bundle.getString("lang_menu"));
skMenuItem.setText(bundle.getString("lang_sk"));
huMenuItem.setText(bundle.getString("lang_hu"));
flag.setIcon((Icon) bundle.getObject("flag"));
lbl.setText(bundle.getString("description"));
setTitle(bundle.getString("name"));
pack();
}
private void createMenuBar() {
JMenuBar menubar = new JMenuBar();
langMenu = new JMenu();
langMenu.setMnemonic(KeyEvent.VK_F);
ButtonGroup btnGroup = new ButtonGroup();
skMenuItem = new JRadioButtonMenuItem("Slovak", true);
btnGroup.add(skMenuItem);
skMenuItem.addActionListener((ActionEvent e) -> {
updateLanguage(new Locale("sk", "SK"));
});
langMenu.add(skMenuItem);
huMenuItem = new JRadioButtonMenuItem("Hungarian");
btnGroup.add(huMenuItem);
huMenuItem.addActionListener((ActionEvent e) -> {
updateLanguage(new Locale("hu", "HU"));
});
langMenu.add(huMenuItem);
menubar.add(langMenu);
setJMenuBar(menubar);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setHorizontalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addComponent(arg[1])
);
gl.setVerticalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
.addComponent(arg[1])
);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ResourceBundleEx3 ex = new ResourceBundleEx3();
ex.setVisible(true);
});
}
}
我们有一个菜单栏,其中的菜单包含两个单选按钮菜单项。 选择单选按钮菜单项会更改应用用户界面的语言。
private void updateLanguage(Locale locale) {
bundle = ResourceBundle.getBundle("com.zetcode.myres.MyResources", locale);
langMenu.setText(bundle.getString("lang_menu"));
skMenuItem.setText(bundle.getString("lang_sk"));
huMenuItem.setText(bundle.getString("lang_hu"));
flag.setIcon((Icon) bundle.getObject("flag"));
lbl.setText(bundle.getString("description"));
setTitle(bundle.getString("name"));
pack();
}
当我们选择单选按钮菜单项时,将调用updateLanguage()
方法。 它根据给定的语言环境创建一个新的ResourceBundle
,并更新菜单,单选菜单项,图像图标,说明和框架标题。
skMenuItem.addActionListener((ActionEvent e) -> {
updateLanguage(new Locale("sk", "SK"));
});
选择斯洛伐克单选按钮菜单项,我们调用updateLanguage()
方法并传递斯洛伐克语言环境作为参数。
图:Swing 应用
Spring Boot 应用
在下一个示例中,我们在 Spring Boot 应用中使用资源包。 Spring 是流行的 Java 应用框架。 Spring Boot 是一种新的解决方案,可以轻松创建基于生产级别的独立 Spring 应用。
图:Spring Boot 应用的 NetBeans 项目结构
同样,我们创建三个属性文件,并将它们放置在src/main/resources/messages
目录中。
words.properties
w1 = Earth
w2 = ocean
这是默认属性文件。
words_de.properties
w1 = Erde
w2 = ozean
The words_de.properties
file contains words in German language.
words_sk.properties
w1 = Zem
w2 = oceán
The words_de.properties
file contains words in Slovak language.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootMessagesEx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<name>SpringBootMessagesEx</name>
</project>
pom.xml
文件包含 Spring Boot 框架的依赖项。
Application.java
package com.zetcode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.ResourceBundleMessageSource;
@SpringBootApplication
public class Application {
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("messages/words");
source.setUseCodeAsDefaultMessage(true);
return source;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Application
是主要的应用类。 我们设置了 Spring Boot 程序。
@Bean
public ResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource source = new ResourceBundleMessageSource();
source.setBasenames("messages/words");
source.setUseCodeAsDefaultMessage(true);
return source;
}
使用@Bean
注解,我们生成了一个ResourceBundleMessageSource
bean,该 bean 由 Spring 容器管理。 ResourceBundleMessageSource
是一种MessageSource
实现,它使用指定的基本名称访问资源束。 此类依赖于基础 JDK 的ResourceBundle
实现。
MyRunner.java
package com.zetcode;
import java.util.Locale;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
@Component
public class MyRunner implements CommandLineRunner {
@Autowired
private MessageSource messageSource;
@Override
public void run(String... args) throws Exception {
System.out.println(messageSource.getMessage("w1",
null, Locale.GERMAN));
System.out.println(messageSource.getMessage("w1",
null, Locale.ENGLISH));
System.out.println(messageSource.getMessage("w2",
null, new Locale("sk", "SK")));
}
}
MyRunner
是 Spring Boot 应用的命令行运行程序。
@Autowired
private MessageSource messageSource;
我们将MessageSource
注入到该字段中。
System.out.println(messageSource.getMessage("w1",
null, Locale.GERMAN));
我们使用getMessage()
方法在德语语言环境中得到单词w1
。
...
Erde
Earth
oceán
...
这是应用的输出。
在本教程中,我们介绍了 Java ResourceBundle
。 我们创建了两个 Java 控制台应用,一个 Swing 应用和一个 Spring Boot 应用。 您可能还需要查看相关的教程: Spring MessageSource
教程, Java Swing 教程, Java 教程或 Java 图像显示教程 。
{% raw %}
Jtwig 教程
原文:http://zetcode.com/java/jtwig/
这是 Jtwig Java 模板引擎的入门教程。 我们介绍 Jtwig 模板引擎,并创建几个控制台和 Web 应用。 Maven 用于构建我们的示例。 NetBeans 用于管理应用。
Jtwig 是 Java 编程语言的现代模板引擎。 它是模块化,可配置且易于使用的模板引擎。 它的灵感来自 Django 的模板引擎。 Jtwig 的主页是 jtwig.org 。
模板引擎将静态数据与动态数据结合起来以产生内容。 模板是内容的中间表示。 它指定如何生成输出。
模板引擎的优点是:
- 关注点分离,
- 避免重复代码,
- 更容易在视图之间切换,
- 可重用性。
Jtwig 不限于 HTML 页面的模板; 它也可以用于纯文本。
控制台应用
前四个应用是控制台应用。 我们在 NetBeans 中创建新的 Maven Java 应用。 他们使用以下 Maven 构建文件:
Excerpt from pom.xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.jtwig</groupId>
<artifactId>jtwig-core</artifactId>
<version>5.58</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
在 Maven pom.xml
文件中,我们为 Jtwig 和jtwig-core
和slf4j-simple
依赖项指定仓库。
变量
在第一个应用中,我们探索 Jtwig set
命令。 set
命令允许在 Jtwig 模板中指定分配操作。 它将表达式的结果分配给指定的变量。
图:NetBeans 中的 Java 控制台项目结构
这是 NetBeans 中的项目结构。
ConsoleJtwigEx.java
package com.zetcode.client;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;
public class ConsoleJtwigEx {
public static void main(String[] args) {
JtwigTemplate template
= JtwigTemplate.classpathTemplate("templates/simple.twig");
JtwigModel model = JtwigModel.newModel();
template.render(model, System.out);
}
}
我们创建一个处理 Jtwig 模板文件的 Java 控制台应用。
JtwigTemplate template
= JtwigTemplate.classpathTemplate("templates/simple.twig");
创建了JtwigTemplate
。 它加载位于src/main/java/resources/templates
目录中的simple.twig
模板文件。
JtwigModel model = JtwigModel.newModel();
JtwigModel
已创建。 该模型是键和值对的容器,这些键和值对与模板结合生成输出。
template.render(model, System.out);
render()
方法创建最终输出。 它使用模型并将其输出到系统输出。
simple.twig
{% set v = 3 + 3 %}
{{ v -}}
{% set a = [1, 2, 3] %}
{{ a[1] -}}
{% set m = { k1: "apple", k2: "banana"} %}
{{ m["k1"] }}
在simple.twig
文件中,我们使用set
命令定义三个变量并显示它们。
{% set v = 3 + 3 %}
Jtwig 代码岛以{%
开头,以%}
结尾。
{{ v -}}
Jtwig 使用{{
和}}
显示表达式和变量的值。 -
是可选的空格控制字符。 在这里,它删除了代码岛后的空白。
6
2
apple
这是应用的输出。
你好应用
在第二个应用中,我们将一个变量传递给模板。
ConsoleJtwigEx2.java
package com.zetcode.client;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;
public class ConsoleJtwigEx2 {
public static void main(String[] args) {
JtwigTemplate template =
JtwigTemplate.classpathTemplate("templates/hello.twig");
JtwigModel model = JtwigModel.newModel().with("name", "Peter");
template.render(model, System.out);
}
}
使用with()
方法,我们将变量传递给模板文件。
hello.twig
Hello {{ name }}
在模板中,我们显示变量。
Hello Peter
这是示例的输出。
传递列表
在下一个应用中,我们将值列表传递给模板。
Excerpt from pom.xml
<dependencies>
<dependency>
<groupId>org.jtwig</groupId>
<artifactId>jtwig-core</artifactId>
<version>5.58</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
</dependencies>
除了jtwi-core
和slf4j-simple
库外,我们还将guava
库添加到项目依赖项中。
ConsoleJtwigEx3.java
package com.zetcode.client;
import com.google.common.collect.Lists;
import java.util.List;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;
public class ConsoleJtwigEx3 {
public static void main(String[] args) {
List<String> names = Lists.newArrayList("Jan", "Peter", "Jane");
JtwigTemplate template =
JtwigTemplate.classpathTemplate("templates/friends.twig");
JtwigModel model = JtwigModel.newModel().with("names", names);
template.render(model, System.out);
}
}
在代码示例中,我们将名称列表传递给模板。
List<String> names = Lists.newArrayList("Jan", "Peter", "Jane");
使用 Google Guava,我们创建了一个名称列表。
JtwigModel model = JtwigModel.newModel().with("names", names);
该列表将传递到模板。
friends.twig
{% for name in names %}
{{ name }}
{% endfor %}
使用for
命令,我们浏览列表并显示其元素。
包含模板
使用include
命令,我们可以包含其他模板文件。
图:NetBeans 中的 Java 控制台项目结构 2
templates
目录中有三个文件。
ConsoleEx4.java
package com.zetcode.client;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;
public class ConsoleJtwigEx4 {
public static void main(String[] args) {
JtwigTemplate template
= JtwigTemplate.classpathTemplate("templates/main.twig");
JtwigModel model = JtwigModel.newModel();
template.render(model, System.out);
}
}
该示例加载main.twig
模板,其中包括其他两个模板。
foot.twig
Footer.
这是foot.twig
模板。
head.twig
Header.
这是head.twig
模板。
main.twig
{% include "classpath:/templates/head.twig" ignore missing %}
Main content.
{% include 'classpath:/templates/foot.twig' ignore missing %}
这是main.twig
模板。 它包括foot.twig
和head.twig
模板。
Header.
Main content.
Footer.
This is the output of the example.
Jtwig servlet 示例
在下面的示例中,我们在标准 Java Web 应用中使用 Jtwig。 该应用打包到war
文件中,并部署在 NetBeans 的内置 Tomcat 服务器上。
在 NetBeans 中,我们创建一个新的 Maven Web 应用。
图:NetBeans 中的 Jtwig servlet 项目结构
这是 NetBeans 中 Jtwig servlet 示例的项目结构。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/JtwigServlet"/>
这是context.xml
文件。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>ServletJtwigEx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<name>ServletJtwigEx</name>
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jtwig</groupId>
<artifactId>jtwig-web</artifactId>
<version>1.52</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
这是pom.xml
文件。 我们使用jtwig-web
依赖项。
JtwigServlet.java
package com.zetcode;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jtwig.web.servlet.JtwigRenderer;
@WebServlet(name = "JtwigServlet", urlPatterns = {""})
public class JtwigServlet extends HttpServlet {
private final JtwigRenderer renderer = JtwigRenderer.defaultRenderer();
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
renderer.dispatcherFor("/WEB-INF/templates/index.twig.html")
.with("greet", "Jtwig servlet example")
.render(request, response);
}
}
我们设置 servlet 并将其分发到模板文件。 我们将greet
变量传递给模板。
@WebServlet(name = "JtwigServlet", urlPatterns = {""})
JtwigServlet
映射到应用的上下文根。
index.twig.html
{{ greet }}
index.twig.html
文件位于WEB-INF/templates
目录中。 模板显示greet
变量。
图:Jtwig servlet 示例
我们在 Opera 网络浏览器中显示应用输出。 NetBeans 中的内置 Tomcat 在 8084 端口上运行。
Spring Boot
Spring 是流行的 Java 应用框架。 Spring Boot 是通过最小的努力来创建独立的,生产级的基于 Spring 的应用的产物。
Spring Boot 命令行应用
在下一个应用中,我们将 Jtwig 集成到 Spring Boot 命令行应用中。 它是放置在 Spring Boot 框架中的控制台应用。
图:NetBeans 中的 Spring Boot 项目结构
这是在 NetBeans 中使用 Jtwig 的 Spring Boot 应用的项目结构。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootJtwigConsoleEx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.jtwig</groupId>
<artifactId>jtwig-core</artifactId>
<version>5.58</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<name>SpringBootJtwigConsoleEx</name>
</project>
这是 Maven 构建文件。 它包括 Spring Boot 和 Jtwig 的依赖项。
SpringBootClient.java
package com.zetcode.client;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration
@ComponentScan(basePackages="com.zetcode")
public class SpringBootClient {
public static void main(String[] args) {
SpringApplication.run(SpringBootClient.class, args);
}
}
SpringBootClient
设置 Spring Boot 应用。 @EnableAutoConfiguration
注解启用 Spring Application Context 的自动配置,尝试猜测和配置我们可能需要的 bean。
MyRunner.java
package com.zetcode.client;
import org.jtwig.JtwigModel;
import org.jtwig.JtwigTemplate;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class MyRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
JtwigTemplate template =
JtwigTemplate.classpathTemplate("templates/greet.twig");
JtwigModel model = JtwigModel.newModel().with("name", "Peter");
template.render(model, System.out);
}
}
MyRunner
是 Spring Boot 应用的命令行运行程序。 我们加载并渲染模板。
greet.twig
Hello {{name}}!
这是greet.twig
模板文件。
Hello Peter!
This is the output of the application.
Spring Boot Web 应用
本教程的最后一个示例使用 Jtwig 模板引擎创建了一个 Spring Boot Web 应用。 请注意,我们正在 NetBeans 中创建 Java SE Maven 应用,而不是 Java Web Maven 应用。 这是因为我们已将 Tomcat 嵌入到我们的 JAR 文件中。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootJtwigWebEx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath />
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependency>
<groupId>org.jtwig</groupId>
<artifactId>jtwig-spring-boot-starter</artifactId>
<version>5.55</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
</dependencies>
</project>
在pom.xml
文件中,我们具有以下依赖项:spring-boot-starter
,spring-web
,jtwig-spring-boot-starter
和slf4j-simple
。
MyController.java
package com.zetcode.controller;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@EnableAutoConfiguration
public class MyController {
@RequestMapping("/{name}")
public String indexAction (ModelMap model, @PathVariable("name") String name) {
model.addAttribute("name", name);
return "index";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(MyController.class, args);
}
}
这是 Spring Boot Web 应用的控制器类。 控制器从请求中读取一个属性,并将其放入模型中。 然后,控制器将映射解析为模板文件。
index.twig
Hello {{name}}!
这是index.twig
文件。
图:Spring Boot Web 示例
Spring Boot 启动一个嵌入式 Tomcat 服务器,监听端口 8080。
本教程专门针对 Jtwig 模板引擎。 您可能也对相关教程感兴趣: FreeMarker 教程, Java 教程,游戏简介, Spark 简介或 Stripes 介绍。
{% endraw %}
Java Servlet 教程
原文:http://zetcode.com/java/servlet/
Java Servlet 教程展示了如何在 Java 中创建简单的 servlet。 我们使用嵌入式 Jetty 服务器。
Java Servlet
Servlet 是响应网络请求的 Java 类。 这主要是一个 HTTP 请求。 Java servlet 用于创建 Web 应用。 它们在 servlet 容器(例如 Tomcat 或 Jetty)中运行。 现代 Java Web 开发使用在 servlet 之上构建的框架。 例如,Spring 或 Vaadin 框架使用 servlet。
javax.servlet
和javax.servlet.http
包提供用于编写servlet 的接口和类。
Java Servlet 示例
在下面的示例中,我们使用@WebServlet
注解创建 Java Servlet。 或者,可以在web.xml
文件中创建映射。
pom.xml
src
├───main
│ ├───java
│ │ └───com
│ │ └───zetcode
│ │ HelloServlet.java
│ ├───resources
│ └───webapp
│ index.html
└───test
└───java
这是项目结构。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>helloservlet</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>9.4.14.v20181114</version>
</plugin>
</plugins>
</build>
</project>
这是 Maven POM 文件。 javax.servlet-api
提供 Servlet API。 provided
范围使依赖项在编译时可用,并指示其在运行时已可用。 已包含在 Servlet 容器(如 Jetty)中。
maven-war-plugin
负责收集 Web 应用的所有工件依赖项,类和资源,并将它们打包到 Web 应用存档中。 jetty-maven-plugin
允许我们与mvn jetty:run
一起运行嵌入式 Jetty 服务器。
com/zetcode/HelloServlet.java
package com.zetcode;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
response.setContentType("text/plain;charset=UTF-8");
var out = response.getOutputStream();
out.print("Hello there from Servlet");
}
}
HelloServlet
将一条简单的文本消息返回给客户端。
@WebServlet(name = "HelloServlet", urlPatterns = {"/hello"})
Java 类用@WebServlet
注解修饰。 它映射到hello
URL 模式。
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
GET 请求调用doGet()
方法。 该方法接收HttpServletRequest
和HttpServletResponse
对象。
response.setContentType("text/plain;charset=UTF-8");
Servlet 以纯文本格式发送输出数据,并且数据的编码设置为 UTF-8。
var out = response.getOutputStream();
通过getOutputStream()
方法,我们获得了 servlet 输出流。 注意,我们不关闭输出流。 这是容器的任务。
out.print("Hello there from Servlet");
我们使用print()
方法编写一条短信。
webapp/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home page</title>
</head>
<body>
<p>
This is home page. Call <a href="/hello">HelloServlet</a>
</p>
</body>
</html>
在主页中,我们有一个调用 servlet 的链接。
$ mvn jetty:run
我们运行嵌入式 Jetty 服务器,并将浏览器导航到localhost:8080
。
在本教程中,我们展示了如何使用嵌入式 Jetty 服务器创建一个简单的 Java Servlet。
您可能也对以下相关教程感兴趣: Java 套接字教程和 Java 教程。
列出所有 Java 教程。
Java 套接字教程
原文:http://zetcode.com/java/socket/
Java 套接字教程展示了如何使用套接字在 Java 中进行网络编程。 套接字编程是低级的。 本教程的目的是介绍包括这些低级详细信息的网络编程。 有些高级 API 可能更适合实际任务。 例如,Java 11 引入了HttpClient
,而 Spring 具有Webclient
。
Java 套接字
在编程中,套接字是网络上运行的两个程序之间的通信端点。 套接字类用于在客户端程序和服务器程序之间创建连接。 Socket
代表客户端套接字,ServerSocket
代表服务器套接字。
注意:在网络中,“套接字”一词具有不同的含义。 它用于 IP 地址和端口号的组合。
ServerSocket
绑定到端口号,这是通过客户端和服务器同意进行通信的唯一 ID。
Socket
和ServerSocket
用于 TCP 协议。 DatagramSocket
和DatagramPacket
用于 UDP 协议。
TCP 更可靠,具有大量错误检查并需要更多资源。 HTTP,SMTP 或 FTP 等服务使用它。 UDP 的可靠性要差得多,错误检查的能力也有限,所需资源也更少。 VoIP 等服务使用它。
DatagramSocket
是用于发送和接收数据报包的套接字。 数据报包由DatagramPacket
类表示。 在数据报套接字上发送或接收的每个数据包都经过单独寻址和路由。 从一台机器发送到另一台机器的多个数据包可能会以不同的方式路由,并且可能以任何顺序到达。
Java 套接字时间客户端
是提供当前时间的服务器。 客户端无需任何命令即可直接连接到服务器,服务器以当前时间作为响应。
注意:时间服务器来来往往,因此我们可能需要在 https://www.ntppool.org/en/ 上找到可用的服务器。
在我们的示例中,我们选择了瑞典的服务器。
com/zetcode/SocketTimeClient.java
package com.zetcode;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
// time servers come and go; we might need to
// find a functioning server on https://www.ntppool.org/en/
public class SocketTimeClient {
public static void main(String[] args) throws IOException {
var hostname = "3.se.pool.ntp.org";
int port = 13;
try (var socket = new Socket(hostname, port)) {
try (var reader = new InputStreamReader(socket.getInputStream())) {
int character;
var output = new StringBuilder();
while ((character = reader.read()) != -1) {
output.append((char) character);
}
System.out.println(output);
}
}
}
}
该示例连接到时间服务器并接收当前时间。
var hostname = "3.se.pool.ntp.org";
int port = 13;
这是瑞典的时间服务器; 13 端口是白天服务的标准端口。
try (var socket = new Socket(hostname, port)) {
流客户端套接字已创建。 它连接到命名主机上的指定端口号。 使用 Java 的try-with-resources
语句自动关闭套接字。
try (var reader = new InputStreamReader(socket.getInputStream())) {
getInputStream()
返回此套接字的输入流。 我们从此输入流中读取服务器的响应。 套接字之间的通信以字节为单位; 因此,我们将InputStreamReader
用作字节和字符之间的桥梁。
int character;
var output = new StringBuilder();
while ((character = reader.read()) != -1) {
output.append((char) character);
}
System.out.println(output);
由于响应消息很小,因此我们可以逐个字符地读取它,而对性能的影响很小。
Java 套接字 Whois 客户端
Whois 是基于 TCP 的面向事务的查询/响应协议,被广泛用于向互联网用户提供信息服务。 它用于查询域名或 IP 地址块所有者等信息。
注意:大多数 Whois 服务器仅提供有限的信息(例如,仅针对选定的域名),并且有关域名所有者的信息通常由域名注册机构匿名。
Whois 协议使用端口 43。
com/zetcode/WhoisClientEx.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
// probing whois.iana.org might give the right
// whois server
public class WhoisClientEx {
public static void main(String[] args) throws IOException {
var domainName = "example.com";
var whoisServer = "whois.nic.me";
int port = 43;
try (var socket = new Socket(whoisServer, port)) {
try (var writer = new PrintWriter(socket.getOutputStream(), true)) {
writer.println(domainName);
try (var reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
}
}
在该示例中,我们探查了有关域名所有者的信息。
try (var writer = new PrintWriter(socket.getOutputStream(), true)) {
writer.println(domainName);
...
我们获得套接字的输出流,并将其包装到PrintWriter
中。 PrintWriter
会将我们的字符转换为字节。 使用println()
,将域名写入流中。 通过套接字的通信被缓冲。 PrintWriter
的第二个参数是autoFlush
; 如果设置为true
,则在每个println()
之后将刷新缓冲区。
try (var reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
来自服务器的响应被读取并写入控制台。
Java 套接字 GET 请求
在下面的示例中,我们创建一个 GET 请求。 HTTP GET 请求用于检索特定资源。
com/zetcode/JavaSocketGetRequest.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class SocketGetRequest {
public static void main(String[] args) throws IOException {
try (var socket = new Socket("webcode.me", 80)) {
try (var wtr = new PrintWriter(socket.getOutputStream())) {
// create GET request
wtr.print("GET / HTTP/1.1\r\n");
wtr.print("Host: www.webcode.me\r\n");
wtr.print("\r\n");
wtr.flush();
socket.shutdownOutput();
String outStr;
try (var bufRead = new BufferedReader(new InputStreamReader(
socket.getInputStream()))) {
while ((outStr = bufRead.readLine()) != null) {
System.out.println(outStr);
}
socket.shutdownInput();
}
}
}
}
}
该示例从网站检索 HTML 页面。
try (var socket = new Socket("webcode.me", 80)) {
我们在端口 80 上的指定网页上打开一个套接字。HTTP 协议使用端口 80。
try (var wtr = new PrintWriter(socket.getOutputStream())) {
我们将在协议上发布文本命令; 因此,我们为套接字输出流创建一个PrintWriter
。 由于我们没有将autoFlush
选项设置为true
,因此我们需要手动刷新缓冲区。
// create GET request
wtr.print("GET / HTTP/1.1\r\n");
wtr.print("Host: www.webcode.me\r\n");
wtr.print("\r\n");
wtr.flush();
我们创建一个 HTTP GET 请求,该请求检索指定网页的主页。 请注意,文本命令以\r\n
(CRLF)字符完成。 这些是必需的通信详细信息,在 RFC 2616 文档中进行了描述。
socket.shutdownOutput();
shutdownOutput
禁用此套接字的输出流。 最后必须关闭连接。
try (var bufRead = new BufferedReader(new InputStreamReader(
socket.getInputStream()))) {
对于服务器响应,我们打开一个套接字输入流,并使用InputStreamReader
将字节转换为字符。 我们还缓冲读取操作。
while ((outStr = bufRead.readLine()) != null) {
System.out.println(outStr);
}
我们逐行读取数据。
socket.shutdownInput();
最后,我们也关闭了输入流。
Java 套接字 HEAD 请求
在下一个示例中,我们使用 Java 套接字创建 HEAD 请求。 HEAD 方法与 GET 方法相同,不同之处在于服务器在响应中不返回消息正文。 它仅返回标头。
com/zetcode/SocketHeadRequest.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class SocketHeadRequest {
public static void main(String[] args) throws IOException {
var hostname = "webcode.me";
int port = 80;
try (var socket = new Socket(hostname, port)) {
try (var writer = new PrintWriter(socket.getOutputStream(), true)) {
writer.println("HEAD / HTTP/1.1");
writer.println("Host: " + hostname);
writer.println("User-Agent: Console Http Client");
writer.println("Accept: text/html");
writer.println("Accept-Language: en-US");
writer.println("Connection: close");
writer.println();
try (var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
}
}
该示例检索指定网页的标题。
writer.println("HEAD / HTTP/1.1");
我们发出 HEAD 命令。
writer.println("Connection: close");
在 HTTP 协议版本 1.1 中,除非另有声明,否则所有连接均被视为持久连接(保持活动状态)。 通过将选项设置为false
,我们通知我们要在请求/响应周期之后完成连接。
Java ServerSocket
日期服务器
下面的示例使用ServerSocket
创建一个非常简单的服务器。 ServerSocket
创建绑定到指定端口的服务器套接字。
com/zetcode/DateServer.java
package com.zetcode;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.time.LocalDate;
public class DateServer {
public static void main(String[] args) throws IOException {
int port = 8081;
try (var listener = new ServerSocket(port)) {
System.out.printf("The started on port %d%n", port);
while (true) {
try (var socket = listener.accept()) {
try (var pw = new PrintWriter(socket.getOutputStream(), true)) {
pw.println(LocalDate.now());
}
}
}
}
}
}
该示例创建一个返回当前日期的服务器。 最后必须手动终止该程序。
int port = 8081;
try (var listener = new ServerSocket(port)) {
在端口 8081 上创建一个服务器套接字。
try (var socket = listener.accept()) {
accept()
方法监听与此套接字建立的连接并接受它。 该方法将阻塞,直到建立连接为止。
try (var pw = new PrintWriter(socket.getOutputStream(), true)) {
pw.println(LocalDate.now());
}
我们将当前日期写入套接字输出流。
get_request.py
#!/usr/bin/env python3
import socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("localhost" , 8081))
s.sendall(b"GET / HTTP/1.1\r\nHost: localhost\r\nAccept: text/html\r\n\r\n")
print(str(s.recv(4096), 'utf-8'))
我们有一个 Python 脚本向服务器发出 GET 请求。
$ get_request.py
2019-07-15
这是输出。
Java 套接字客户端/服务器示例
在下面的示例中,我们有一个服务器和一个客户端。 服务器反转从客户端发送的文本。 这个例子很简单而且很阻塞。 为了改善它,我们需要包括线程。
com/zetcode/ReverseServer.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
// This server communicates only with one client at a time.
// It must disconnect from a client first to communicate
// with another client. It receives a bye command from a client
// to close a connection.
public class ReverseServer {
public static void main(String[] args) throws IOException {
int port = 8081;
try (var serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
while (true) {
try (var socket = serverSocket.accept()) {
System.out.println("client connected");
try (var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
var writer = new PrintWriter(socket.getOutputStream(), true)) {
String text;
do {
text = reader.readLine();
if (text != null) {
var reversed = new StringBuilder(text).reverse().toString();
writer.println("Server: " + reversed);
System.out.println(text);
}
} while (!"bye".equals(text));
System.out.println("client disconnected");
}
}
}
}
}
}
ReverseServer
将反向字符串发送回客户端。 一次仅与一个客户端通信。 它必须首先与客户端断开连接才能与另一个客户端通信。 它从客户端收到一个bye
命令以关闭连接。
try (var reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
var writer = new PrintWriter(socket.getOutputStream(), true)) {
我们有一个套接字输入流,用于读取客户端数据,以及套接字输出流,用于将响应发送回客户端; 输出流和连接已关闭。
do {
text = reader.readLine();
if (text != null) {
var reversed = new StringBuilder(text).reverse().toString();
writer.println("Server: " + reversed);
System.out.println(text);
}
} while (!"bye".equals(text));
为单个客户端创建了一个do-while
循环。 我们从客户端读取数据,然后将修改后的内容发送回去。 收到客户端的再见命令后,循环结束。 在此之前,没有其他客户端可以连接到服务器。
com/zetcode/ReverseClient.java
package com.zetcode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
// the client must send a bye command to
// inform the server to close the connection
public class ReverseClient {
public static void main(String[] args) throws IOException {
var hostname = "localhost";
int port = 8081;
try (var socket = new Socket(hostname, port)) {
try (var writer = new PrintWriter(socket.getOutputStream(), true)) {
try (var scanner = new Scanner(System.in)) {
try (var reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()))) {
String command;
do {
System.out.print("Enter command: ");
command = scanner.nextLine();
writer.println(command);
var data = reader.readLine();
System.out.println(data);
} while (!command.equals("bye"));
}
}
}
}
}
}
客户端将文本数据发送到服务器。
do {
System.out.print("Enter command: ");
command = scanner.nextLine();
writer.println(command);
var data = reader.readLine();
System.out.println(data);
} while (!command.equals("bye"));
我们从控制台读取输入,并将其发送到服务器。 当我们发送bye
命令时,while
循环完成,该命令通知服务器可以关闭连接。
Java DatagramSocket
示例
UDP 是一种通信协议,它通过网络传输独立的数据包,不保证到达且也不保证传递顺序。 使用 UDP 的一项服务是每日报价(QOTD)。
下面的示例创建一个连接到 QOTD 服务的客户端程序。
com/zetcode/DatagramSocketEx.java
package com.zetcode;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
// DatagramSocket provides network communication via UDP protocol
// The Quote of the Day (QOTD) service is a member of the Internet protocol
// suite, defined in RFC 865
public class DatagramSocketEx {
public static void main(String[] args) throws IOException {
var hostname = "djxmmx.net";
int port = 17;
var address = InetAddress.getByName(hostname);
try (var socket = new DatagramSocket()) {
var request = new DatagramPacket(new byte[1], 1, address, port);
socket.send(request);
var buffer = new byte[512];
var response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
var quote = new String(buffer, 0, response.getLength());
System.out.println(quote);
}
}
}
该示例从报价服务检索报价并将其打印到终端。
var address = InetAddress.getByName(hostname);
我们从主机名获得 IP 地址。
try (var socket = new DatagramSocket()) {
创建了DatagramSocket
。
var request = new DatagramPacket(new byte[1], 1, address, port);
创建了DatagramPacket
。 由于 QOTD 服务不需要来自客户端的数据,因此我们发送了一个空的小型数组。 每次发送数据包时,我们都需要指定数据,地址和端口。
socket.send(request);
数据包通过send()
发送到其目的地。
var buffer = new byte[512];
var response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
我们收到来自服务的数据包。
var quote = new String(buffer, 0, response.getLength());
System.out.println(quote);
我们将接收到的字节转换为字符串并打印。
在本教程中,我们创建了带有套接字的网络 Java 程序。 您可能也对相关教程感兴趣: Java HTTP GET/POST 请求教程, Java InputStreamReader
教程, Java Servlet 教程和 Java 教程 。
列出所有 Java 教程。
FreeMarker 教程
原文:http://zetcode.com/java/freemarker/
这是 FreeMarker Java 模板引擎的入门教程。 我们介绍了 FreeMarker 模板引擎,并创建了几个控制台和 Web 应用。 Maven 用于构建我们的示例。 NetBeans 用于管理应用。
目录
FreeMarker 是 Java 编程语言的模板引擎。 模板以 FreeMarker 模板语言(FTL)编写。 FreeMarker 的主页是 freemarker.org 。
FreeMarker 模板引擎
模板引擎将静态数据与动态数据结合起来以产生内容。 模板是内容的中间表示。 它指定如何生成输出。
模板引擎的优点是:
- 关注点分离,
- 避免重复代码,
- 更容易在视图之间切换,
- 可重用性。
按照惯例,FreeMarker 模板文件的扩展名为.ftl
。
FreeMarker 不仅限于 HTML 页面的模板; 它可以用于生成电子邮件,配置文件,源代码等。
FreeMarker 控制台应用
前两个应用是控制台应用。 我们在 NetBeans 中创建新的 Maven Java 应用。 他们使用以下 Maven 构建文件:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
在 Maven pom.xml
文件中,我们指定 FreeMarker 依赖项。
FreeMarker 插值
插值是放在${ }
字符之间的表达式。 FreeMarker 会将输出中的插值替换为大括号内表达式的实际值。
在下面的示例中,我们使用 FreeMarker 模板文件来生成简单的文本输出。
图:NetBeans 中的 Java 控制台项目结构
这是 NetBeans 中的项目结构。
FreeMarkerConsoleEx.java
package com.zetcode;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
public class FreeMarkerConsoleEx {
public static void main(String[] args) throws IOException,
TemplateException {
Configuration cfg = new Configuration(new Version("2.3.23"));
cfg.setClassForTemplateLoading(FreeMarkerConsoleEx.class, "/");
cfg.setDefaultEncoding("UTF-8");
Template template = cfg.getTemplate("test.ftl");
Map<String, Object> templateData = new HashMap<>();
templateData.put("msg", "Today is a beautiful day");
try (StringWriter out = new StringWriter()) {
template.process(templateData, out);
System.out.println(out.getBuffer().toString());
out.flush();
}
}
}
该示例将简单文本打印到控制台。 最终文本由模板引擎处理。
Configuration cfg = new Configuration(new Version("2.3.23"));
Configuration
用于设置 FreeMarker 设置; 它以 FreeMarker 库的版本为参数。
cfg.setClassForTemplateLoading(FreeMarkerConsoleEx.class, "/");
setClassForTemplateLoading()
设置将使用其方法来加载模板的类。
Template template = cfg.getTemplate("test.ftl");
使用getTemplate()
方法,我们检索test.ftl
模板文件。
Map<String, Object> templateData = new HashMap<>();
templateData.put("msg", "Today is a beautiful day");
数据模型已创建。 来自模型的数据将被动态放置到 FreeMarker 模板文件中。
try (StringWriter out = new StringWriter()) {
template.process(templateData, out);
System.out.println(out.getBuffer().toString());
out.flush();
}
process()
方法使用提供的数据模型执行模板,并将生成的输出写入提供的写入器。
test.ftl
The message is: ${msg}
test.ftl
模板文件包含一个插值; 它将替换为生成的字符串。
The message is: Today is a beautiful day
这是应用的输出。
用 FreeMarker 列出一个集合
#list
指令列出了数据集合。
下一个示例生成汽车列表。
Car.java
package com.zetcode.bean;
public class Car {
private String name;
private int price;
public Car() {
}
public Car(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
我们有一个Car
bean。 它具有两个属性:名称和价格。
FreeMarkerConsoleEx2.java
package com.zetcode;
import com.zetcode.bean.Car;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FreeMarkerConsoleEx2 {
public static void main(String[] args) throws IOException,
TemplateException {
Configuration cfg = new Configuration(new Version("2.3.23"));
cfg.setClassForTemplateLoading(FreeMarkerConsoleEx2.class, "/");
cfg.setDefaultEncoding("UTF-8");
Template template = cfg.getTemplate("test.ftl");
Map<String, Object> templateData = new HashMap<>();
Car c1 = new Car("Audi", 52642);
Car c2 = new Car("Volvo", 29000);
Car c3 = new Car("Skoda", 9000);
List<Car> cars = new ArrayList<>();
cars.add(c1);
cars.add(c2);
cars.add(c3);
templateData.put("cars", cars);
try (StringWriter out = new StringWriter()) {
template.process(templateData, out);
System.out.println(out.getBuffer().toString());
out.flush();
}
}
}
此示例是一个 Java 控制台程序,该程序使用 FreeMarker 动态创建包含汽车列表的文本输出。
Map<String, Object> templateData = new HashMap<>();
Car c1 = new Car("Audi", 52642);
Car c2 = new Car("Volvo", 29000);
Car c3 = new Car("Skoda", 9000);
List<Car> cars = new ArrayList<>();
cars.add(c1);
cars.add(c2);
cars.add(c3);
templateData.put("cars", cars);
在这里,我们创建Car
对象的ArrayList
并将其放入数据模型。
test.ftl
<#list cars as car>
${car.name}: ${car.price}
</#list>
模板文件包含一个#list
指令,该指令打印汽车对象的属性。 使用点字符访问属性。
Audi: 52,642
Volvo: 29,000
Skoda: 9,000
这是示例的输出。
FreeMarker 指令
FreeMarker 指令是执行动作的特殊标记。 指令有两种:内置指令和自定义指令。
<#assign>
标签创建一个新的普通变量。 可以使用${}
构造对其进行访问。 变量在模板中创建。 如果数据模型中有一个同名变量,则模板变量会将其隐藏。
assignment.ftl
<#assign name = "Robert">
His name is ${name}.
<#assign>
指令创建一个新的name
变量。 变量的值使用${name}
语法打印。
His name is Robert.
该示例打印此行。
可以使用<#if>
,<#elseif>
和<#else>
伪指令完成模板节的条件处理。
conditions.ftl
<#assign value = 4>
<#if value < 0>
The number is negative
<#elseif value == 0>
The number is zero
<#else>
The number is positive
</#if>
该示例创建一个新的value
变量,并使用条件指令来测试该值。
The number is positive
这是输出。
<#list>
指令用于遍历序列。
listing.ftl
<#assign colours = ["red", "green", "blue", "yellow"]>
<#list colours as col>
${col}
</#list>
在示例中,我们将新的颜色名称序列分配给colours
变量。 <#list>
指令遍历集合并打印每个项目。
red
green
blue
yellow
该示例给出了此输出。
listing2.ftl
<#assign items = {"pens": 3, "cups": 2, "tables": 1}>
<#list items?values as v>
${v}
</#list>
<#list items?keys as k>
${k}
</#list>
在此示例中,我们创建一个哈希变量,并使用<#list>
输出哈希的值和键。
3
2
1
pens
cups
tables
The example gives this output.
当我们使用对空格不敏感的格式(例如 HTML 或 XML)时,<#compress>
指令会删除多余的空格
compressing.ftl
<#assign value="\t\tweather\n\n">
<#compress>
${value}
Today is a wonderful day.
1 2 3 4 5
</#compress>
我们的文本带有空格,制表符和换行符。
weather
Today is a wonderful day.
1 2 3 4 5
该程序删除了所有多余的空白。
FreeMarker Servlet 示例
在下面的示例中,我们在标准 Java Web 应用中使用 FreeMarker。 该应用打包到war
文件中,并部署在 NetBeans 的内置 Tomcat 服务器上。
在 NetBeans 中,我们创建一个新的 Maven Web 应用。
图:NetBeans 中的 FreeMarker servlet 项目结构
这是 NetBeans 中 FreeMarker servlet 示例的项目结构。
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-web-api</artifactId>
<version>7.0</version>
<scope>provided</scope>
</dependency>
在pom.xml
文件中,我们具有这两个依赖关系。
context.xml
<?xml version="1.0" encoding="UTF-8"?>
<Context path="/FreemarkerServletEx"/>
这是context.xml
文件。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
<servlet>
<servlet-name>freemarker</servlet-name>
<servlet-class>freemarker.ext.servlet.FreemarkerServlet</servlet-class>
<init-param>
<param-name>TemplatePath</param-name>
<param-value>/</param-value>
</init-param>
<init-param>
<param-name>NoCache</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>ResponseCharacterEncoding</param-name>
<param-value>fromTemplate</param-value>
</init-param>
<init-param>
<param-name>ExceptionOnMissingTemplate</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>incompatible_improvements</param-name>
<param-value>2.3.23</param-value>
</init-param>
<init-param>
<param-name>template_exception_handler</param-name>
<param-value>html_debug</param-value>
</init-param>
<init-param>
<param-name>template_update_delay</param-name>
<param-value>0 s</param-value>
</init-param>
<init-param>
<param-name>default_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>output_encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>locale</param-name>
<param-value>en_US</param-value>
</init-param>
<init-param>
<param-name>number_format</param-name>
<param-value>0.##########</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>freemarker</servlet-name>
<url-pattern>*.ftl</url-pattern>
</servlet-mapping>
<security-constraint>
<web-resource-collection>
<web-resource-name>FreeMarker MVC Views</web-resource-name>
<url-pattern>*.ftl</url-pattern>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
在web.xml
文件中,我们设置并配置了 FreeMarker servlet。 有关每个选项的说明,请参考 FreeMarker 文档。
Car.java
package com.zetcode.bean;
public class Car {
private String name;
private int price;
public Car() {
}
public Car(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
这是Car
bean,具有有关汽车对象的基本数据。
MyServlet.java
package com.zetcode.web;
import com.zetcode.bean.Car;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(name = "MyServlet", urlPatterns = {"/"})
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
Car c1 = new Car("Audi", 52642);
Car c2 = new Car("Volvo", 29000);
Car c3 = new Car("Skoda", 9000);
List<Car> cars = new ArrayList<>();
cars.add(c1);
cars.add(c2);
cars.add(c3);
request.setAttribute("cars", cars);
request.getRequestDispatcher("/index.ftl").forward(request, response);
}
}
我们设置 servlet 并将其分发到模板文件。 我们创建了一个ArrayList
汽车并将其设置为请求。
index.ftl
<!DOCTYPE html>
<html>
<head>
<title>FreeMarker test</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<table>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
<#list cars as car>
<tr>
<td>${car.name}</td>
<td>${car.price}</td>
</tr>
</#list>
</table>
</body>
</html>
index.ftl
文件位于src/main/webapp
目录中。 使用#list
指令,我们显示了cars
系列的所有元素。
图:FreeMarker servlet 示例
我们在 Opera 网络浏览器中显示应用输出。 NetBeans 中的内置 Tomcat 在 8084 端口上运行。
FreeMarker 与 Spark
Spark 是为快速开发而构建的简单轻便的 Java Web 框架。 默认情况下,Spark 在嵌入式 Jetty Web 服务器上运行,但可以配置为在其他 Web 服务器上运行。 为了将 FreeMarker 与 Spark 集成,我们使用spark-template-freemarker
,这是 Spark 的 Freemarker 模板引擎实现。
$ tree
.
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── zetcode
│ │ └── SparkFreeMarker.java
│ └── resources
│ └── views
│ └── hello.ftl
└── test
└── java
这是项目结构。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SparkFreeMarker</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-template-freemarker</artifactId>
<version>2.5.5</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.5.5</version>
</dependency>
</dependencies>
<build>
<finalName>SparkFreeMarker</finalName>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.zetcode.SparkFreeMarker</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom.xml
文件包含 Spark 模块和 FreeMarker 的依赖项。
SparkFreeMarker.java
package com.zetcode;
import freemarker.template.Configuration;
import freemarker.template.Version;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import spark.ModelAndView;
import spark.Request;
import spark.Response;
import static spark.Spark.get;
import spark.template.freemarker.FreeMarkerEngine;
public class SparkFreeMarker {
public static void main(String[] args) throws IOException {
Configuration conf = new Configuration(new Version(2, 3, 23));
conf.setClassForTemplateLoading(SparkFreeMarker.class, "/views");
get("/hello/:name", SparkFreeMarker::message, new FreeMarkerEngine(conf));
}
public static ModelAndView message(Request req, Response res) {
Map<String, Object> params = new HashMap<>();
params.put("name", req.params(":name"));
return new ModelAndView(params, "hello.ftl");
}
}
我们为 FreeMarker 设置了相同的应用。
Configuration conf = new Configuration(new Version(2, 3, 23));
conf.setClassForTemplateLoading(SparkFreeMarker.class, "/views");
我们用Configuration
类配置 FreeMarker。 模板文件将放置在views
目录中,该目录必须位于类路径上。
get("/hello/:name", SparkFreeMarker::message, new FreeMarkerEngine(conf));
FreeMarkerEngine
传递给get()
方法。
public static ModelAndView message(Request req, Response res) {
Map<String, Object> params = new HashMap<>();
params.put("name", req.params(":name"));
return new ModelAndView(params, "hello.ftl");
}
ModelAndView
用于设置视图名称和要渲染的模型对象。
hello.ftl
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Hello ${name}</p>
</body>
</html>
这是hello.ftl
模板文件; 它引用随ModelAndView
对象传递的名称变量。
$ mvn package
我们使用mvn package
命令构建项目。
$ java -jar target/SparkFreeMarkejar-with-dependencies.jar
我们运行程序。 maven-assembly-plugin
允许创建具有所有依赖项的可执行 JAR。
$ curl localhost:4567/hello/Thomas
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Hello Thomas</p>
</body>
</html>
这是输出。
FreeMarker 与 Spring
Spring 是流行的 Java 应用框架。 Spring Boot 是通过最小的努力来创建独立的,生产级的基于 Spring 的应用的产物。
经典 Spring 应用
在以下示例中,我们将 FreeMarker 集成到经典的 Spring 应用中。
├── pom.xml
└── src
├── main
│ ├── java
│ │ └── com
│ │ └── zetcode
│ │ ├── service
│ │ │ ├── IVersionService.java
│ │ │ └── VersionService.java
│ │ └── web
│ │ └── MyController.java
│ ├── resources
│ │ └── my.properties
│ └── webapp
│ ├── META-INF
│ │ └── context.xml
│ └── WEB-INF
│ ├── spring-servlet.xml
│ ├── views
│ │ ├── index.ftl
│ │ └── version.ftl
│ └── web.xml
└── test
└── java
这是我们经典的 Spring 应用的项目结构。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>ClassicSpringFreeMarker</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>ClassicSpringFreeMarker</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.3</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
pom.xml
文件包含 Spring 模块和 FreeMarker 的依赖项。
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<servlet>
<servlet-name>spring</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>spring</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
</web-app>
在web.xml
文件中,我们定义了 Spring DispatcherServlet
,它是 HTTP 请求处理器的中央调度器。
spring-servlet.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.zetcode"/>
<context:property-placeholder location="classpath*:my.properties"/>
<!--freemarker config-->
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/views/"/>
</bean>
<bean id="viewResolver" class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
<property name="cache" value="true"/>
<property name="prefix" value=""/>
<property name="suffix" value=".ftl"/>
</bean>
</beans>
在 spring servlet 上下文 XML 文件中,我们定义了两个 bean:freemarkerConfig
和viewResolver
。 这些是 FreeMarker 的配置 bean。 spring-servlet.xml
位于WEB-INF
中。
<context:component-scan base-package="com.zetcode" />
Spring 将扫描com.zetcode
包中的组件。
<context:property-placeholder location="classpath*:my.properties"/>
<context:property-placeholder>
元素注册一个PropertySourcesPlaceholderConfigurer
,该元素允许使用@Value
注解设置属性。 location
属性指示在哪里查找属性。
my.properties
app.version: "1.0"
在my.properties
文件中,我们有一个键/值对。 该值是应用的版本。 该文件位于usr/main/resources
目录中。
IVersionService.java
package com.zetcode.service;
public interface IVersionService {
public String getVersion();
}
IVersionService
接口包含一个方法协定:getVersion()
。
VersionService.java
package com.zetcode.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
@Service
public class VersionService implements IVersionService {
@Value("${app.version}")
private String appVersion;
@Override
public String getVersion() {
return appVersion;
}
}
VersionService
返回应用的版本。
@Value("${app.version}")
private String appVersion;
位于my.properties
文件中的app.version
键的值被注入到appVersion
属性中。
MyController.java
package com.zetcode.web;
import com.zetcode.service.VersionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
@Controller
public class MyController {
@Autowired
private VersionService versionService;
@RequestMapping("/index")
public String index(Model model) {
return "index";
}
@RequestMapping(value = "/version", method = RequestMethod.GET)
public ModelAndView version() {
String version = versionService.getVersion();
ModelAndView model = new ModelAndView("version");
model.addObject("version", version);
return model;
}
}
这是控制器类。
@RequestMapping(value = "/version", method = RequestMethod.GET)
public ModelAndView version() {
String version = versionService.getVersion();
ModelAndView model = new ModelAndView("version");
model.addObject("version", version);
return model;
}
在version
方法(当带有/version
URL 的请求到达时被调用)中,我们调用VersionService
的getVersion()
方法并将该值传递到ModelAndView
中。 处理将分派到version.ftl
模板文件,在该文件中插入版本值。
index.ftl
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Show application version <a href="version.html">version</a></p>
</body>
</html>
这是index.ftl
文件。 它具有一个链接,该链接向服务器发送请求以获取应用的版本。
version.ftl
<!DOCTYPE html>
<html>
<head>
<title>Home page</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<p>Application version: ${version}</p>
</body>
</html>
version.ftl
模板文件用于建立服务器对客户端的响应。
使用 FreeMarker 的 Spring Boot Web 应用
在下一个应用中,我们将 FreeMarker 集成到 Spring Boot Web 应用中。
图:NetBeans 中的 Spring Boot Web 项目结构
这是在 NetBeans 中使用 FreeMarker 的 Spring Boot Web 应用的项目结构。 请注意,我们正在 NetBeans 中创建 Java SE Maven 应用,而不是 Java Web Maven 应用。 这是因为我们已将 Tomcat 嵌入到我们的 JAR 文件中。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.zetcode</groupId>
<artifactId>SpringBootFreemarkerEx</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
这是 Maven 构建文件。 它包括 Spring Boot 和 FreeMarker 的依赖项。 无需在 Spring Boot 中配置 FreeMarker。 在 POM 文件中找到 FreeMarker 依赖关系后,Spring Boot 会自动进行配置。 spring-boot-maven-plugin
创建带有嵌入式容器(默认为 Tomcat)的可执行 JAR。
SpringBootClient.java
package com.zetcode.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Application
设置 Spring Boot 应用。 @SpringBootApplication
注解执行三件事:1)将类定义为配置类,2)启用自动配置,3)启用组件扫描。
MyController.java
package com.zetcode.web;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class MyController {
@GetMapping("/")
public String index(Model model) {
return "index";
}
@GetMapping("/hello")
public String hello(Model model, @RequestParam(value="msg", required=false,
defaultValue="Freemarker") String msg) {
model.addAttribute("message", msg);
return "hello";
}
}
这是 Spring Boot Web 应用的控制器类。 控制器具有两个映射。 第一个映射解析为index.ftl
文件,第二个映射解析为hello.ftl
文件。
index.ftl
<!DOCTYPE html>
<html>
<head>
<title>Spring Boot Form</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<form action="/hello" method="get">
<p>Message: <input type="text" name="msg"></p>
<p>
<input type="submit" value="Submit">
<input type="reset" value="Reset">
</p>
</form>
</body>
</html>
这是index.ftl
文件。 它具有 HTML 表单,可将消息发送到服务器。
hello.ftl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Freemarker example</title>
</head>
<body>
<p>${message}<p>
</body>
</html>
服务器以一条消息回应给客户端。 响应是从hello.ftl
模板文件创建的。
图:Spring Boot Web 示例
Spring Boot 启动一个嵌入式 Tomcat 服务器,监听端口 8080。
本教程专门针对 FreeMarker 模板引擎。 您可能也对相关教程感兴趣: Servlet FreeMarker JDBCTemplate
教程, Spring Boot Groovy CLI 教程, Jtwig 教程, Java 教程 ,游戏简介, Spark 简介或 Stripes 简介。
Android 教程
原文:http://zetcode.com/mob/android/
这是 Android 开发教程。 本教程适合初学者。 阅读完本教程后,您将可以编写非平凡的 Android 应用。
目录
安卓系统
Android 是基于 Linux 的操作系统,专为智能手机和平板电脑等移动设备设计。 它还用于各种网络设备,智能电视系统,手表和家用电器。 Android 的应用是使用 Java 编程语言的自定义版本开发的。
相关教程
Java 教程涵盖了 Java 编程语言。
Java EE 5 教程
原文:http://zetcode.com/tutorials/jeetutorials/
这些是 Java Enterprise Edition 5 教程。 (J2EE 教程)。 这些 Java EE 教程适合 Java EE 新手程序员。 这套教程的目的是使您开始使用 Java Enterprise Edition 平台。
目录
- 简介
- 安装 Java
- 安装 NetBeans
- Java 应用服务器
- CGIServlet
- JavaServer 页面,JSP
- JSP 中的隐式对象
- 购物车骨架
- MySQL 数据库
- Servlet
- 在 Servlet 中发送电子邮件
- 在 Servlet 中创建验证码
- 数据源 &
DriverManager
- 处理异常
- Java Beans
- 自定义标签
- 使用 iBATIS 的对象关系映射
Java EE
Java 平台企业版是用于开发,部署和管理服务器端企业级应用的一组库,工具,规范,最佳实践。
JSoup 教程
原文:http://zetcode.com/java/jsoup/
JSoup 教程是 JSoup HTML 解析器的入门指南。 在本教程中,我们将解析 HTML 字符串,本地 HTML 文件和网页中的 HTML 数据。 我们将清理数据并执行 Google 搜索。
JSoup
JSoup 是用于提取和处理 HTML 数据的 Java 库。 它实现了 HTML5 规范,并将 HTML 解析为与现代浏览器相同的 DOM。 该项目的网站是 jsoup.org 。
JSoup 功能
使用 JSoup,我们能够:
- 从 URL,文件或字符串中抓取并解析 HTML
- 使用 DOM 遍历或 CSS 选择器查找和提取数据
- 处理 HTML 元素,属性和文本
- 根据安全的白名单清除用户提交的内容,以防止 XSS 攻击
- 输出整洁的 HTML
JSoup Maven 依赖项
在本教程的示例中,我们使用以下 Maven 依赖关系。
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
JSoup
类
JSoup
类通过其静态方法为 jsoup 函数提供了核心公共访问点。 例如,clean()
方法清理 HTML 代码,connect()
方法创建与 URL 的连接,或者parse()
方法解析 HTML 内容。
JSoup 解析 HTML 字符串
在第一个示例中,我们将解析一个 HTML 字符串。
com/zetcode/JSoupFromStringEx.java
package com.zetcode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class JSoupFromStringEx {
public static void main(String[] args) {
String htmlString = "<html><head><title>My title</title></head>"
+ "<body>Body content</body></html>";
Document doc = Jsoup.parse(htmlString);
String title = doc.title();
String body = doc.body().text();
System.out.printf("Title: %s%n", title);
System.out.printf("Body: %s", body);
}
}
该示例分析 HTML 字符串并输出其标题和正文内容。
String htmlString = "<html><head><title>My title</title></head>"
+ "<body>Body content</body></html>";
此字符串包含简单的 HTML 数据。
Document doc = Jsoup.parse(htmlString);
使用Jsoup
的parse()
方法,我们解析 HTML 字符串。 该方法返回一个 HTML 文档。
String title = doc.title();
文档的title()
方法获取文档的title
元素的字符串内容。
String body = doc.body().text();
文档的body()
方法返回body
元素; 其text()
方法获取元素的文本。
JSoup 解析本地 HTML 文件
在第二个示例中,我们将解析本地 HTML 文件。 我们使用重载的Jsoup.parse()
方法,该方法将File
对象作为其第一个参数。
index.html
<!DOCTYPE html>
<html>
<head>
<title>My title</title>
<meta charset="UTF-8">
</head>
<body>
<div id="mydiv">Contents of a div element</div>
</body>
</html>
对于示例,我们使用上面的 HTML 文件。
com/zetcode/JSoupFromFileEx.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
public class JSoupFromFileEx {
public static void main(String[] args) throws IOException {
String fileName = "src/main/resources/index.html";
Document doc = Jsoup.parse(new File(fileName), "utf-8");
Element divTag = doc.getElementById("mydiv");
System.out.println(divTag.text());
}
}
该示例将分析src/main/resources/
目录中的index.html
文件。
Document doc = Jsoup.parse(new File(fileName), "utf-8");
我们使用Jsoup.parse()
方法解析 HTML 文件。
Element divTag = doc.getElementById("mydiv");
使用文档的getElementById()
方法,我们通过元素的 ID 获得元素。
System.out.println(divTag.text());
使用元素的text()
方法检索标签的文本。
JSoup 读取网站的标题
在以下示例中,我们将抓取并解析网页,然后检索title
元素的内容。
com/zetcode/JSoupTitleEx.java
package com.zetcode;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class JSoupTitleEx {
public static void main(String[] args) throws IOException {
String url = "http://webcode.me";
Document doc = Jsoup.connect(url).get();
String title = doc.title();
System.out.println(title);
}
}
在代码示例中,我们读取了指定网页的标题。
Document doc = Jsoup.connect(url).get();
Jsoup 的connect()
方法创建到给定 URL 的连接。 get()
方法执行 GET 请求并解析结果; 它返回一个 HTML 文档。
String title = doc.title();
使用文档的title()
方法,我们获得 HTML 文档的标题。
JSoup 读取 HTML 源码
下一个示例检索网页的 HTML 源。
com/zetcode/JSoupHTMLSourceEx.java
package com.zetcode;
import java.io.IOException;
import org.jsoup.Jsoup;
public class JSoupHTMLSourceEx {
public static void main(String[] args) throws IOException {
String webPage = "http://webcode.me";
String html = Jsoup.connect(webPage).get().html();
System.out.println(html);
}
}
该示例打印网页的 HTML。
String html = Jsoup.connect(webPage).get().html();
html()
方法返回元素的 HTML; 在我们的案例中,整个文档的 HTML 源代码。
JSoup 获取信息
HTML 文档的元信息提供有关网页的结构化元数据,例如其描述和关键字。
com/zetcode/JSoupMetaInfoEx.java
package com.zetcode;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
public class JSoupMetaInfoEx {
public static void main(String[] args) throws IOException {
String url = "http://www.jsoup.org";
Document document = Jsoup.connect(url).get();
String description = document.select("meta[name=description]").first().attr("content");
System.out.println("Description : " + description);
String keywords = document.select("meta[name=keywords]").first().attr("content");
System.out.println("Keywords : " + keywords);
}
}
该代码示例检索有关指定网页的元信息。
String keywords = document.select("meta[name=keywords]").first().attr("content");
文档的select()
方法查找与给定查询匹配的元素。 first()
方法返回第一个匹配的元素。 使用attr()
方法,我们获得content
属性的值。
JSoup 解析链接
下一个示例分析 HTML 页面中的链接。
com/zetcode/JSoupLinksEx.java
package com.zetcode;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JSoupLinksEx {
public static void main(String[] args) throws IOException {
String url = "http://jsoup.org";
Document document = Jsoup.connect(url).get();
Elements links = document.select("a[href]");
for (Element link : links) {
System.out.println("link : " + link.attr("href"));
System.out.println("text : " + link.text());
}
}
}
在该示例中,我们连接到网页并解析其所有链接元素。
Elements links = document.select("a[href]");
要获取链表,我们使用文档的select()
方法。
JSoup 清理 HTML 数据
Jsoup 提供了用于清理 HTML 数据的方法。
com/zetcode/JSoupSanitizeEx.java
package com.zetcode;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.safety.Cleaner;
import org.jsoup.safety.Whitelist;
public class JSoupSanitizeEx {
public static void main(String[] args) {
String htmlString = "<html><head><title>My title</title></head>"
+ "<body><center>Body content</center></body></html>";
boolean valid = Jsoup.isValid(htmlString, Whitelist.basic());
if (valid) {
System.out.println("The document is valid");
} else {
System.out.println("The document is not valid.");
System.out.println("Cleaned document");
Document dirtyDoc = Jsoup.parse(htmlString);
Document cleanDoc = new Cleaner(Whitelist.basic()).clean(dirtyDoc);
System.out.println(cleanDoc.html());
}
}
}
在示例中,我们清理并清理 HTML 数据。
String htmlString = "<html><head><title>My title</title></head>"
+ "<body><center>Body content</center></body></html>";
HTML 字符串包含不推荐使用的center
元素。
boolean valid = Jsoup.isValid(htmlString, Whitelist.basic());
isValid()
方法确定字符串是否为有效的 HTML。 白名单是可以通过清除程序的 HTML(元素和属性)列表。 Whitelist.basic()
定义了一组基本的干净 HTML 标记。
Document dirtyDoc = Jsoup.parse(htmlString);
Document cleanDoc = new Cleaner(Whitelist.basic()).clean(dirtyDoc);
借助Cleaner
,我们清理了脏的 HTML 文档。
The document is not valid.
Cleaned document
<html>
<head></head>
<body>
Body content
</body>
</html>
这是程序的输出。 我们可以看到中心元素已被删除。
JSoup 执行 Google 搜索
以下示例使用 Jsoup 执行 Google 搜索。
com/zetcode/JsoupGoogleSearchEx.java
package com.zetcode;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
public class JsoupGoogleSearchEx {
private static Matcher matcher;
private static final String DOMAIN_NAME_PATTERN
= "([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,15}";
private static Pattern patrn = Pattern.compile(DOMAIN_NAME_PATTERN);
public static String getDomainName(String url) {
String domainName = "";
matcher = patrn.matcher(url);
if (matcher.find()) {
domainName = matcher.group(0).toLowerCase().trim();
}
return domainName;
}
public static void main(String[] args) throws IOException {
String query = "Milky Way";
String url = "https://www.google.com/search?q=" + query + "&num=10";
Document doc = Jsoup
.connect(url)
.userAgent("Jsoup client")
.timeout(5000).get();
Elements links = doc.select("a[href]");
Set<String> result = new HashSet<>();
for (Element link : links) {
String attr1 = link.attr("href");
String attr2 = link.attr("class");
if (!attr2.startsWith("_Zkb") && attr1.startsWith("/url?q=")) {
result.add(getDomainName(attr1));
}
}
for (String el : result) {
System.out.println(el);
}
}
}
该示例为"Milky Way"
一词创建搜索请求。 它会打印十个与该术语匹配的域名。
private static final String DOMAIN_NAME_PATTERN
= "([a-zA-Z0-9]([a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,15}";
private static Pattern patrn = Pattern.compile(DOMAIN_NAME_PATTERN);
Google 搜索返回长链接,我们要从中获取域名。 为此,我们使用正则表达式模式。
public static String getDomainName(String url) {
String domainName = "";
matcher = patrn.matcher(url);
if (matcher.find()) {
domainName = matcher.group(0).toLowerCase().trim();
}
return domainName;
}
getDomainName()
使用正则表达式匹配器从搜索链接返回域名。
String query = "Milky Way";
这是我们的搜索词。
String url = "https://www.google.com/search?q=" + query + "&num=10";
这是执行 Google 搜索的网址。
Document doc = Jsoup
.connect(url)
.userAgent("Jsoup client")
.timeout(5000).get();
我们连接到 URL,设置 5s 超时,然后发送 GET 请求。 返回 HTML 文档。
Elements links = doc.select("a[href]");
从文档中,我们选择链接。
Set<String> result = new HashSet<>();
for (Element link : links) {
String attr1 = link.attr("href");
String attr2 = link.attr("class");
if (!attr2.startsWith("_Zkb") && attr1.startsWith("/url?q=")) {
result.add(getDomainName(attr1));
}
}
我们寻找不具有class="_Zkb"
属性并且具有href="/url?q="
属性的链接。 请注意,这些是硬编码的值,将来可能会更改。
for (String el : result) {
System.out.println(el);
}
最后,我们将域名打印到终端。
en.wikipedia.org
www.space.com
www.nasa.gov
sk.wikipedia.org
www.bbc.co.uk
imagine.gsfc.nasa.gov
www.forbes.com
www.milkywayproject.org
www.youtube.com
www.universetoday.com
这些是"Milky Way"
一词的 Google 顶级搜索结果。
本教程专门针对 Jsoup HTML 解析器。
您可能也对相关教程感兴趣: Java 教程,用 Java 读取网页,用 Java 阅读文本文件或 Jtwig 教程。
列出所有 Java 教程。
JFreeChart 教程
原文:http://zetcode.com/java/jfreechart/
在本教程中,我们学习如何使用 JFreeChart。 我们展示了如何创建各种类型的图表。 图表显示在 Swing 应用中,并保存到图像文件中。 我们使用 Java Servlet 在 Web 浏览器中创建和呈现图表,并从 MySQL 数据库检索图表数据。
JFreeChart 库
图表是一种以简单方式显示信息的图形,通常使用直线和曲线来显示金额。 JFreeChart 是用于创建图表的流行 Java 库。 JFreeChart 允许创建各种交互式和非交互式图表。 我们可以创建折线图,条形图,面积图,散点图,饼图,甘特图和各种专用图,例如风向图或气泡图。
JFreeChart 可以广泛地定制; 它允许修改图表项目的颜色和绘制,图例,线条或标记的样式。 它会自动绘制轴刻度和图例。 图表具有内置功能,可使用鼠标放大。 现有的图表可以通过库在其数据集合上具有的监听器轻松更新。 它支持多种输出格式,包括 PNG,JPEG,PDF 和 SVG。
JFreeChart 由 David Gilbert 于 2000 年创立。如今,JFreeChart 是 Java 开发者中使用最广泛的图表库。
JFreeChart Maven 依赖项
<dependency>
<groupId>org.jfree</groupId>
<artifactId>jfreechart</artifactId>
<version>1.0.19</version>
</dependency>
对于我们的项目,我们使用此 Maven 依赖项。
JFreeChart 折线图
折线图是一种基本类型的图表,它将信息显示为由直线段连接的一系列数据点。 JavaFX 中的折线图是使用ChartFactory.createXYLineChart()
创建的。
LineChartEx.java
package com.zetcode.linechartex;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class LineChartEx extends JFrame {
public LineChartEx() {
initUI();
}
private void initUI() {
XYDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
chartPanel.setBackground(Color.white);
add(chartPanel);
pack();
setTitle("Line chart");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private XYDataset createDataset() {
XYSeries series = new XYSeries("2016");
series.add(18, 567);
series.add(20, 612);
series.add(25, 800);
series.add(30, 980);
series.add(40, 1410);
series.add(50, 2350);
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series);
return dataset;
}
private JFreeChart createChart(XYDataset dataset) {
JFreeChart chart = ChartFactory.createXYLineChart(
"Average salary per age",
"Age",
"Salary (€)",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
XYPlot plot = chart.getXYPlot();
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setSeriesPaint(0, Color.RED);
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
plot.setRenderer(renderer);
plot.setBackgroundPaint(Color.white);
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
plot.setDomainGridlinesVisible(true);
plot.setDomainGridlinePaint(Color.BLACK);
chart.getLegend().setFrame(BlockBorder.NONE);
chart.setTitle(new TextTitle("Average Salary per Age",
new Font("Serif", java.awt.Font.BOLD, 18)
)
);
return chart;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
LineChartEx ex = new LineChartEx();
ex.setVisible(true);
});
}
}
在示例中,我们创建了一个折线图,显示每个年龄段的平均工资。
XYSeries series = new XYSeries("2016");
series.add(18, 567);
series.add(20, 612);
series.add(25, 800);
...
XYSeries
表示形式为(x, y)
的零个或多个数据项的序列。
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series);
该系列将添加到XYSeriesCollection
中,该XYSeriesCollection
是可以用作数据集的XYSeries
对象的集合。
JFreeChart chart = ChartFactory.createXYLineChart(
"Average salary per age",
"Age",
"Salary (€)",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
ChartFactory.createXYLineChart()
创建一个新的折线图。 该方法的参数是:图表标题,X 轴标签,Y 轴标签,数据,绘图方向和三个标志,指示是否显示图例,工具提示和 URL。
XYPlot plot = chart.getXYPlot();
我们可以参考该图以对其进行自定义。
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setSeriesPaint(0, Color.RED);
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
plot.setRenderer(renderer);
在这里,我们为图表线设置了描边和颜色。 XYLineAndShapeRenderer
是将数据点与线连接和/或在每个数据点绘制形状的对象。 渲染器通过setRenderer()
方法设置。
plot.setBackgroundPaint(Color.white);
setBackgroundPaint()
设置绘图区域的背景颜色。
plot.setRangeGridlinesVisible(true);
plot.setRangeGridlinePaint(Color.BLACK);
plot.setDomainGridlinesVisible(true);
plot.setDomainGridlinePaint(Color.BLACK);
我们显示网格线并将其涂成黑色。
chart.getLegend().setFrame(BlockBorder.NONE);
我们删除图例周围的边框。
chart.setTitle(new TextTitle("Average Salary per Age",
new Font("Serif", java.awt.Font.BOLD, 18)
)
);
我们使用新字体创建图表标题。
图:折线图
带有两个序列的折线图
在第二个示例中,我们创建具有两个数据序列的折线图。
LineChartEx2.java
package com.zetcode.linechartex2;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.block.BlockBorder;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.xy.XYDataset;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class LineChartEx2 extends JFrame {
public LineChartEx2() {
initUI();
}
private void initUI() {
XYDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
chartPanel.setBackground(Color.white);
add(chartPanel);
pack();
setTitle("Line chart");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private XYDataset createDataset() {
XYSeries series1 = new XYSeries("2014");
series1.add(18, 530);
series1.add(20, 580);
series1.add(25, 740);
series1.add(30, 901);
series1.add(40, 1300);
series1.add(50, 2219);
XYSeries series2 = new XYSeries("2016");
series2.add(18, 567);
series2.add(20, 612);
series2.add(25, 800);
series2.add(30, 980);
series2.add(40, 1210);
series2.add(50, 2350);
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
dataset.addSeries(series2);
return dataset;
}
private JFreeChart createChart(final XYDataset dataset) {
JFreeChart chart = ChartFactory.createXYLineChart(
"Average salary per age",
"Age",
"Salary (€)",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
XYPlot plot = chart.getXYPlot();
XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer();
renderer.setSeriesPaint(0, Color.RED);
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
renderer.setSeriesPaint(1, Color.BLUE);
renderer.setSeriesStroke(1, new BasicStroke(2.0f));
plot.setRenderer(renderer);
plot.setBackgroundPaint(Color.white);
plot.setRangeGridlinesVisible(false);
plot.setDomainGridlinesVisible(false);
chart.getLegend().setFrame(BlockBorder.NONE);
chart.setTitle(new TextTitle("Average Salary per Age",
new Font("Serif", Font.BOLD, 18)
)
);
return chart;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
LineChartEx2 ex = new LineChartEx2();
ex.setVisible(true);
});
}
}
该示例绘制具有两个数据系列的折线图。
XYSeries series1 = new XYSeries("2014");
series1.add(18, 530);
series1.add(20, 580);
series1.add(25, 740);
...
我们创建第一个系列; 其中包含 2014 年的数据。
XYSeries series2 = new XYSeries("2016");
series2.add(18, 567);
series2.add(20, 612);
series2.add(25, 800);
...
创建第二个数据系列; 其中包含 2016 年的数据。
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
dataset.addSeries(series2);
通过addSeries()
方法将系列添加到XYSeriesCollection
。
renderer.setSeriesPaint(0, Color.RED);
renderer.setSeriesStroke(0, new BasicStroke(2.0f));
renderer.setSeriesPaint(1, Color.BLUE);
renderer.setSeriesStroke(1, new BasicStroke(2.0f));
一根线涂成红色,另一根线涂成蓝色。
plot.setRangeGridlinesVisible(false);
plot.setDomainGridlinesVisible(false);
网格线已关闭。
图:具有两个系列的折线图
将图表保存到图像
ChartUtilities
是 JFreeChart 的工具方法的集合。 它包括将图表转换为图像格式并创建简单的 HTML 图像映射的方法。
LineChartToPNGEx.java
package com.zetcode.linecharttopngex;
import java.io.File;
import java.io.IOException;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.xy.XYSeries;
import org.jfree.data.xy.XYSeriesCollection;
public class LineChartToPNGEx {
public static void main(String[] args) throws IOException {
XYSeries series1 = new XYSeries("2014");
series1.add(18, 530);
series1.add(20, 580);
series1.add(25, 740);
series1.add(30, 901);
series1.add(40, 1300);
series1.add(50, 2219);
XYSeriesCollection dataset = new XYSeriesCollection();
dataset.addSeries(series1);
JFreeChart chart = ChartFactory.createXYLineChart(
"Average salary per age",
"Age",
"Salary (€)",
dataset,
PlotOrientation.VERTICAL,
true,
true,
false
);
ChartUtilities.saveChartAsPNG(new File("line_chart.png"), chart, 450, 400);
}
}
该示例创建一个折线图并将其保存到 PNG 文件中。
ChartUtilities.saveChartAsPNG(new File("line_chart.png"), chart, 450, 400);
ChartUtilities.saveChartAsPNG()
将图表保存为 PNG 格式的指定文件。
JFreeChart 区域图
区域图以图形方式显示随时间变化的定量数据。 在 JFreeChart 中使用ChartFactory.createAreaChart()
方法创建面积图。
AreaChartEx.java
package com.zetcode.areachartex;
import java.awt.Color;
import java.awt.Font;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.renderer.AreaRendererEndType;
import org.jfree.chart.renderer.category.AreaRenderer;
import org.jfree.chart.title.TextTitle;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.general.DatasetUtilities;
public class AreaChartEx extends JFrame {
public AreaChartEx() {
initUI();
}
private void initUI() {
CategoryDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
chartPanel.setBackground(Color.white);
add(chartPanel);
pack();
setTitle("Area chart");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private CategoryDataset createDataset() {
double[][] data = new double[][]{
{82502, 84026, 85007, 86216, 85559, 84491, 87672,
88575, 89837, 90701}
};
CategoryDataset dataset = DatasetUtilities.createCategoryDataset(
new String[]{"Oil"}, new String[]{"2004", "2005", "2006",
"2007", "2008", "2009", "2010", "2011", "2012", "2013"},
data
);
return dataset;
}
private JFreeChart createChart(CategoryDataset dataset) {
JFreeChart chart = ChartFactory.createAreaChart(
"Oil consumption",
"Time",
"Thousands bbl/day",
dataset,
PlotOrientation.VERTICAL,
false,
true,
true
);
CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.setForegroundAlpha(0.3f);
AreaRenderer renderer = (AreaRenderer) plot.getRenderer();
renderer.setEndType(AreaRendererEndType.LEVEL);
chart.setTitle(new TextTitle("Oil consumption",
new Font("Serif", java.awt.Font.BOLD, 18))
);
return chart;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
AreaChartEx ex = new AreaChartEx();
ex.setVisible(true);
});
}
}
该示例显示了一个区域图,该区域图按年份显示了世界原油消耗量。
double[][] data = new double[][]{
{82502, 84026, 85007, 86216, 85559, 84491, 87672,
88575, 89837, 90701}
};
CategoryDataset dataset = DatasetUtilities.createCategoryDataset(
new String[]{"Oil"}, new String[]{"2004", "2005", "2006",
"2007", "2008", "2009", "2010", "2011", "2012", "2013"},
data
);
使用DatasetUtilities.createCategoryDataset()
方法创建数据集。 与类别关联的类别数据集值。 在我们的示例中,我们有数年与石油消耗有关。
CategoryPlot plot = (CategoryPlot) chart.getPlot();
plot.setForegroundAlpha(0.3f);
我们使用setForegroundAlpha()
方法使图表透明。
AreaRenderer renderer = (AreaRenderer) plot.getRenderer();
renderer.setEndType(AreaRendererEndType.LEVEL);
我们调整图表的结尾。
图:面积图
JFreeChart 条形图
条形图显示带有矩形条的分组数据,其长度与它们所代表的值成比例。 条形图可以垂直或水平绘制。
BarChartEx.java
package com.zetcode.barchartex;
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
public class BarChartEx extends JFrame {
public BarChartEx() {
initUI();
}
private void initUI() {
CategoryDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
chartPanel.setBackground(Color.white);
add(chartPanel);
pack();
setTitle("Bar chart");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private CategoryDataset createDataset() {
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.setValue(46, "Gold medals", "USA");
dataset.setValue(38, "Gold medals", "China");
dataset.setValue(29, "Gold medals", "UK");
dataset.setValue(22, "Gold medals", "Russia");
dataset.setValue(13, "Gold medals", "South Korea");
dataset.setValue(11, "Gold medals", "Germany");
return dataset;
}
private JFreeChart createChart(CategoryDataset dataset) {
JFreeChart barChart = ChartFactory.createBarChart(
"Olympic gold medals in London",
"",
"Gold medals",
dataset,
PlotOrientation.VERTICAL,
false, true, false);
return barChart;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
BarChartEx ex = new BarChartEx();
ex.setVisible(true);
});
}
}
该代码示例使用条形图显示了 2012 年伦敦每个国家/地区的奥运金牌数量。
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.setValue(46, "Gold medals", "USA");
dataset.setValue(38, "Gold medals", "China");
dataset.setValue(29, "Gold medals", "UK");
dataset.setValue(22, "Gold medals", "Russia");
dataset.setValue(13, "Gold medals", "South Korea");
dataset.setValue(11, "Gold medals", "Germany");
我们使用DefaultCategoryDataset
创建数据集。
JFreeChart barChart = ChartFactory.createBarChart(
"Olympic gold medals in London",
"",
"Gold medals",
dataset,
PlotOrientation.VERTICAL,
false, true, false);
使用ChartFactory.createBarChart()
方法创建条形图。
图:条形图
JFreeChart 饼图
饼图是一种圆图,分为多个切片以说明数值比例。 使用 JFreeChart 中的ChartFactory.createPieChart()
方法创建一个饼图。
PieChartEx.java
package com.zetcode.piechartex;
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.data.general.DefaultPieDataset;
public class PieChartEx extends JFrame {
public PieChartEx() {
initUI();
}
private void initUI() {
DefaultPieDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart);
chartPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
chartPanel.setBackground(Color.white);
add(chartPanel);
pack();
setTitle("Pie chart");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private DefaultPieDataset createDataset() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Apache", 52);
dataset.setValue("Nginx", 31);
dataset.setValue("IIS", 12);
dataset.setValue("LiteSpeed", 2);
dataset.setValue("Google server", 1);
dataset.setValue("Others", 2);
return dataset;
}
private JFreeChart createChart(DefaultPieDataset dataset) {
JFreeChart barChart = ChartFactory.createPieChart(
"Web servers market share",
dataset,
false, true, false);
return barChart;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
PieChartEx ex = new PieChartEx();
ex.setVisible(true);
});
}
}
该示例使用饼图来显示 Web 服务器的市场份额。
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Apache", 52);
dataset.setValue("Nginx", 31);
dataset.setValue("IIS", 12);
...
DefaultPieDataset
用于创建数据集。
JFreeChart barChart = ChartFactory.createPieChart(
"Web servers market share",
dataset,
false, true, false);
使用ChartFactory.createPieChart()
方法创建一个新的饼图。
Servlet 中的 JFreeChart
在以下示例中,我们使用 Java servlet 创建饼图。 该图表在 Web 浏览器中呈现。
DoChart.java
package com.zetcode.servletchart;
import java.awt.BasicStroke;
import java.awt.Color;
import java.io.IOException;
import java.io.OutputStream;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.data.general.DefaultPieDataset;
@WebServlet(name = "DoChart", urlPatterns = {"/DoChart"})
public class DoChart extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("image/png");
OutputStream outputStream = response.getOutputStream();
JFreeChart chart = getChart();
int width = 500;
int height = 350;
ChartUtilities.writeChartAsPNG(outputStream, chart, width, height);
}
public JFreeChart getChart() {
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("Croatia", 22);
dataset.setValue("Bohemia", 34);
dataset.setValue("Bulgaria", 18);
dataset.setValue("Spain", 5);
dataset.setValue("Others", 21);
JFreeChart chart = ChartFactory.createPieChart("Popular destinations",
dataset, true, false, false);
chart.setBorderVisible(false);
return chart;
}
}
DoChart
Servlet 创建一个饼图并将其发送到客户端。
response.setContentType("image/png");
setContentType()
将内容设置为 PNG 图像。
OutputStream outputStream = response.getOutputStream();
使用getOutputStream()
方法,我们得到一个输出流。 这是我们向其发送数据的隧道。
ChartUtilities.writeChartAsPNG(outputStream, chart, width, height);
ChartUtilities.writeChartAsPNG()
将二进制数据写入输出字符串。
图:浏览器中的饼图
显示来自 MySQL 数据库的数据
JDBCCategoryDataset
是对数据库 JDBC 结果集的CategoryDataset
实现。 通过使用字符串 SQL 查询调用executeQuery()
来填充数据集。
medals.sql
DROP TABLE IF EXISTS GoldMedalsLondon;
CREATE TABLE GoldMedalsLondon (
Id int(11) NOT NULL AUTO_INCREMENT,
Country text,
Medals int(11) DEFAULT NULL,
PRIMARY KEY (Id)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;
LOCK TABLES GoldMedalsLondon WRITE;
INSERT INTO GoldMedalsLondon VALUES (1,'USA',46),(2,'China',38),(3,'UK',29),
(4,'Russia',22),(5,'South Korea',13),(6,'Germany',11);
UNLOCK TABLES;
我们将这些数据存储在 MySQL 数据库表中。
mysql> SELECT * FROM GoldMedalsLondon;
+----+-------------+--------+
| Id | Country | Medals |
+----+-------------+--------+
| 1 | USA | 46 |
| 2 | China | 38 |
| 3 | UK | 29 |
| 4 | Russia | 22 |
| 5 | South Korea | 13 |
| 6 | Germany | 11 |
+----+-------------+--------+
6 rows in set (0.00 sec)
我们使用mysql
工具显示数据。
MySQLChartEx.java
package com.zetcode.mysqlchartex;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.jdbc.JDBCCategoryDataset;
public class MySQLChartEx {
private static JDBCCategoryDataset dataset;
public static void main(String[] args) throws IOException, SQLException {
Connection con = null;
String url = "jdbc:mysql://localhost:3306/testdb";
String user = "testuser";
String password = "test623";
try {
con = DriverManager.getConnection(url, user, password);
dataset = new JDBCCategoryDataset(con);
dataset.executeQuery("SELECT Country, Medals FROM GoldMedalsLondon");
} finally {
if (con != null) {
con.close();
}
}
JFreeChart barChart = ChartFactory.createBarChart(
"Olympic Gold medals in London",
"",
"Gold medals",
dataset,
PlotOrientation.VERTICAL,
false, true, false);
ChartUtilities.saveChartAsPNG(new File("medals.png"), barChart, 450, 400);
}
}
该示例从 MySQL 表检索数据,创建条形图,然后将其保存到 PNG 图像中。
con = DriverManager.getConnection(url, user, password);
dataset = new JDBCCategoryDataset(con);
JDBCCategoryDataset
已创建; 它以数据库连接为参数。
dataset.executeQuery("SELECT Country, Medals FROM GoldMedalsLondon");
executeQuery()
通过针对现有数据库连接执行提供的查询来填充数据集。 SQL 查询必须至少返回两列。 第一列是类别名称,其余列是值。
JFreeChart barChart = ChartFactory.createBarChart(
"Olympic Gold medals in London",
"",
"Gold medals",
dataset,
PlotOrientation.VERTICAL,
false, true, false);
条形图已创建。
ChartUtilities.saveChartAsPNG(new File("medals.png"), barChart, 450, 400);
条形图通过ChartUtilities.saveChartAsPNG()
方法保存到 PNG 文件中。
本教程专门针对 JFreeChart 库。 您可能也对相关教程感兴趣: Java Swing 教程, Java 2D 教程, Java PDFBox 教程或 Java 教程。 也可以在 JavaFX 中创建图表。
Java 数据类型 II
原文:http://zetcode.com/lang/java/datatypes2/
在 Java 教程的这一部分中,我们将继续介绍 Java 的数据类型。 我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。
Java 包装器类
包装器类是原始数据类型的对象表示。 需要Object
时,包装器类用于表示原始值。 例如,Java 集合仅适用于对象。 它们不能采用原始类型。 包装器类还包括一些有用的方法。 例如,它们包括进行数据类型转换的方法。 将原始类型放入包装器类称为boxing
。 反向过程称为unboxing
。
通常,在有某种理由的情况下,我们使用包装器类。 否则,我们使用原始类型。 包装器类是不可变的。 创建它们后,就无法更改它们。 基本类型比装箱类型更快。 在科学计算和其他大规模数字处理中,包装类可能会严重影响性能。
原始类型 | 包装类 | 构造器参数 |
---|---|---|
byte |
Byte |
byte 或String |
short |
Short |
short 或String |
int |
Integer |
int 或String |
long |
Long |
long 或String |
float |
Float |
float ,double 或String |
double |
Double |
double 或String |
char |
Char |
char |
boolean |
Boolean |
boolean 或String |
Table: Primitive types and their wrapper class equivalents
Integer
类将原始类型int
的值包装在对象中。 它包含在处理int
时有用的常量和方法。
com/zetcode/IntegerWrapper.java
package com.zetcode;
public class IntegerWrapper {
public static void main(String[] args) {
int a = 55;
Integer b = new Integer(a);
int c = b.intValue();
float d = b.floatValue();
String bin = Integer.toBinaryString(a);
String hex = Integer.toHexString(a);
String oct = Integer.toOctalString(a);
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
System.out.println(bin);
System.out.println(hex);
System.out.println(oct);
}
}
本示例适用于Integer
包装器类。
int a = 55;
该行创建一个整数原始数据类型。
Integer b = new Integer(a);
Integer
包装器类是从原始int
类型创建的。
int c = b.intValue();
float d = b.floatValue();
intValue()
方法将Integer
转换为int
。 同样,floatValue()
返回float
数据类型。
String bin = Integer.toBinaryString(a);
String hex = Integer.toHexString(a);
String oct = Integer.toOctalString(a);
这三种方法返回整数的二进制,十六进制和八进制表示形式。
$ java IntegerWrapper.java
55
55
55
55.0
110111
37
67
这是程序输出。
集合是用于处理对象组的强大工具。 原始数据类型不能放入 Java 集合中。 将原始值装箱后,可以将它们放入集合中。
com/zetcode/Numbers.java
package com.zetcode;
import java.util.ArrayList;
import java.util.List;
public class Numbers {
public static void main(String[] args) {
List<Number> ls = new ArrayList<>();
ls.add(1342341);
ls.add(new Float(34.56));
ls.add(235.242);
ls.add(new Byte("102"));
ls.add(new Short("1245"));
for (Number n : ls) {
System.out.println(n.getClass());
System.out.println(n);
}
}
}
在示例中,我们将各种数字放入ArrayList
中。 ArrayList
是动态的,可调整大小的数组。
List<Number> ls = new ArrayList<>();
创建一个ArrayList
实例。 在尖括号中,我们指定容器将容纳的类型。 Number
是 Java 中所有五个数字基本类型的抽象基类。
ls.add(1342341);
ls.add(new Float(34.56));
ls.add(235.242);
ls.add(new Byte("102"));
ls.add(new Short("1245"));
我们将五个数字添加到集合中。 请注意,整数和双精度值未装箱; 这是因为对于整数和双精度类型,编译器将执行自动装箱。
for (Number n : ls) {
System.out.println(n.getClass());
System.out.println(n);
}
我们遍历容器并打印类名称及其每个元素的值。
$ java Numbers.java
class java.lang.Integer
1342341
class java.lang.Float
34.56
class java.lang.Double
235.242
class java.lang.Byte
102
class java.lang.Short
1245
com.zetcode.Numbers
程序给出该输出。 请注意,这两个数字是由编译器自动装箱的。
Java 装箱
从原始类型转换为对象类型称为boxing
。 Unboxing
是相反的操作。 它正在将对象类型转换回原始类型。
com/zetcode/BoxingUnboxing.java
package com.zetcode;
public class BoxingUnboxing {
public static void main(String[] args) {
long a = 124235L;
Long b = new Long(a);
long c = b.longValue();
System.out.println(c);
}
}
在代码示例中,我们将long
值放入Long
对象中,反之亦然。
Long b = new Long(a);
该行执行拳击。
long c = b.longValue();
在这一行,我们进行拆箱。
Java 自动装箱
Java 5 引入了自动装箱。 Autoboxing
是原始类型及其对应的对象包装器类之间的自动转换。 自动装箱使编程更加容易。 程序员不需要手动进行转换。
当一个值是原始类型而另一个值是包装器类时,将执行自动装箱和拆箱:
- 赋值
- 将参数传递给方法
- 从方法返回值
- 比较操作
- 算术运算
Integer i = new Integer(50);
if (i < 100) {
...
}
在if
表达式的方括号内,将Integer
与int
进行比较。 Integer
对象被转换为原始int
类型,并与 100 值进行比较。 自动取消装箱。
com/zetcode/Autoboxing.java
package com.zetcode;
public class Autoboxing {
private static int cube(int x) {
return x * x * x;
}
public static void main(String[] args) {
Integer i = 10;
int j = i;
System.out.println(i);
System.out.println(j);
Integer a = cube(i);
System.out.println(a);
}
}
此代码示例演示了自动装箱和自动拆箱。
Integer i = 10;
Java 编译器在此代码行中执行自动装箱。 将int
值装箱为Integer
类型。
int j = i;
在这里会自动开箱。
Integer a = cube(i);
当我们将Integer
传递给cube()
方法时,便完成了自动拆箱。 当我们返回计算值时,将执行自动装箱,因为int
转换回了Integer
。
Java 语言不支持运算符重载。 当我们对包装类应用算术运算时,自动装箱由编译器完成。
com/zetcode/Autoboxing2.java
package com.zetcode;
public class Autoboxing2 {
public static void main(String[] args) {
Integer a = new Integer(5);
Integer b = new Integer(7);
Integer add = a + b;
Integer mul = a * b;
System.out.println(add);
System.out.println(mul);
}
}
我们有两个Integer
值。 我们对这两个值执行加法和乘法运算。
Integer add = a + b;
Integer mul = a * b;
与 Ruby,C# ,Python,D 或 C++ 等语言不同,Java 没有实现运算符重载。 在这两行中,编译器调用intValue()
方法,并将包装器类转换为int
,然后通过调用valueOf()
方法将结果包装回Integer
。
Java 自动装箱和对象内化
Object intering
仅存储每个不同对象的一个副本。 该对象必须是不可变的。 不同的对象存储在内部池中。 在 Java 中,当将原始值装箱到包装对象中时,将插入某些值(任何布尔值,任何字节,0 到 127 之间的任何char
以及 -128 和 127 之间的任何short
或int
),以及这两个值之一的任意装箱转换可以确保得到相同的对象。
根据 Java 语言规范,这些是最小范围。 因此,行为取决于实现。 对象交互可以节省时间和空间。 从字面值,自动装箱和Integer.valueOf()
中获得的对象是内部对象,而使用new
运算符构造的对象始终是不同的对象。
比较包装类时,对象交互会产生一些重要的后果。 ==
运算符比较对象的引用标识,而equals()
方法比较值。
com/zetcode/Autoboxing3.java
package com.zetcode;
public class Autoboxing3 {
public static void main(String[] args) {
Integer a = 5; // new Integer(5);
Integer b = 5; // new Integer(5);
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));
Integer c = 155;
Integer d = 155;
System.out.println(c == d);
System.out.println(c.equals(d));
System.out.println(c.compareTo(d));
}
}
该示例比较了一些Integer
对象。
Integer a = 5; // new Integer(5);
Integer b = 5; // new Integer(5);
两个整数装在Integer
包装器类中。
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.compareTo(b));
使用三种不同的方法比较这些值。 ==
运算符比较两种盒装类型的引用标识。 由于对象的嵌入,该运算结果为true
。 如果使用new
运算符,则将创建两个不同的对象,并且==
运算符将返回false
。 equals()
方法在数值上比较两个Integer
对象。 它返回布尔值true
或false
(在我们的例子中为true
)。
最后,compareTo()
方法还对两个对象进行了数值比较。 如果此Integer
等于参数Integer
,则返回值 0; 如果此Integer
在数值上小于参数Integer
,则该值小于 0; 如果此Integer
在数值上大于自变量Integer
,则该值大于 0。
Integer c = 155;
Integer d = 155;
我们还有两种盒装类型。 但是,这些值大于内化的最大值(127); 因此,创建了两个不同的对象。 这次==
运算符产生false
。
$ java Autoboxing3.java
true
true
0
false
true
0
这是程序的输出。
Java 空类型
Java 具有特殊的null
类型。 类型没有名称。 结果,不可能声明null
类型的变量或将其强制转换为null
类型。 null
表示一个空引用,不引用任何对象。 null
是引用类型变量的默认值。 不能为原始类型分配null
字面值。
在不同的上下文中,null
表示不存在对象,未知值或未初始化状态。
com/zetcode/NullType.java
package com.zetcode;
import java.util.Random;
public class NullType {
private static String getName() {
Random r = new Random();
boolean n = r.nextBoolean();
if (n == true) {
return "John";
} else {
return null;
}
}
public static void main(String[] args) {
String name = getName();
System.out.println(name);
System.out.println(null == null);
if ("John".equals(name)) {
System.out.println("His name is John");
}
}
}
我们在程序中使用null
值。
private static String getName() {
Random r = new Random();
boolean n = r.nextBoolean();
if (n == true) {
return "John";
} else {
return null;
}
}
在getName()
方法中,我们模拟了一种方法有时可以返回null
值的情况。
System.out.println(null == null);
我们比较两个空值。 表达式返回true
。
if ("John".equals(name)) {
System.out.println("His name is John");
}
我们将名称变量与"John"
字符串进行比较。 注意,我们在"John"
字符串上调用了equals()
方法。 这是因为如果名称变量等于null
,则调用该方法将导致NullPointerException
。
$ java NullType.java
null
true
$ java NullType.java
null
true
$ java NullType.java
John
true
His name is John
我们执行该程序三次。
Java 默认值
编译器会为未初始化的字段提供默认值。 最终字段和局部变量必须由开发者初始化。
下表显示了不同类型的默认值。
数据类型 | 默认值 |
---|---|
byte |
0 |
char |
'\u0000' |
short |
0 |
int |
0 |
long |
0L |
float |
0f |
double |
0d |
Object |
null |
boolean |
false |
Table: Default values for uninitialized instance variables
下一个示例将打印未初始化的实例变量的默认值。 实例变量是在类中定义的变量,该类的每个实例化对象都具有一个单独的副本。
com/zetcode/DefaultValues.java
package com.zetcode;
public class DefaultValues {
static byte b;
static char c;
static short s;
static int i;
static float f;
static double d;
static String str;
static Object o;
public static void main(String[] args) {
System.out.println(b);
System.out.println(c);
System.out.println(s);
System.out.println(i);
System.out.println(f);
System.out.println(d);
System.out.println(str);
System.out.println(o);
}
}
在示例中,我们声明了八个成员字段。 它们未初始化。 编译器将为每个字段设置默认值。
static byte b;
static char c;
static short s;
static int i;
...
这些是实例变量; 它们在任何方法外声明。 这些字段被声明为static
,因为它们是通过static
main()
方法访问的。 (在本教程的后面,我们将更多地讨论静态变量和实例变量。)
$ java DefaultValues.java
0
0
0
0.0
0.0
null
null
This is the output of the program.
Java 类型转换
我们经常一次处理多种数据类型。 将一种数据类型转换为另一种数据类型是编程中的常见工作。 术语类型转换是指将一种数据类型的实体更改为另一种。 在本节中,我们将处理原始数据类型的转换。 引用类型的转换将在本章后面提到。 转换规则很复杂; 它们在 Java 语言规范的第 5 章中指定。
转换有两种类型:隐式转换和显式转换。 隐式类型转换,也称为强制,是编译器自动进行的类型转换。 在显式转换中,程序员直接在一对圆括号内指定转换类型。 显式转换称为类型转换。
转换发生在不同的上下文中:赋值,表达式或方法调用。
int x = 456;
long y = 34523L;
float z = 3.455f;
double w = 6354.3425d;
在这四个分配中,没有转换发生。 每个变量都被分配了预期类型的字面值。
int x = 345;
long y = x;
float m = 22.3354f;
double n = m;
在此代码中,Java 编译器隐式执行了两次转换。 将较小类型的变量分配给较大类型的变量是合法的。 该转换被认为是安全的,因为不会损失任何精度。 这种转换称为隐式加宽转换。
long x = 345;
int y = (int) x;
double m = 22.3354d;
float n = (float) m;
在 Java 中,将较大类型的变量分配给较小类型是不合法的。 即使值本身适合较小类型的范围。 在这种情况下,可能会降低精度。 为了允许这种分配,我们必须使用类型转换操作。 这样,程序员说他是故意这样做的,并且他意识到可能会丢失一些精度这一事实。 这种转换称为显式变窄转换。
byte a = 123;
short b = 23532;
在这种情况下,我们处理一种特定类型的分配转换。 123 和 23532 是整数字面值,a
,b
变量为byte
和short
类型。 可以使用铸造操作,但不是必需的。 字面值可以在赋值左侧的变量中表示。 我们处理隐式变窄转换。
private static byte calc(byte x) {
...
}
byte b = calc((byte) 5);
以上规则仅适用于分配。 当我们将整数字面值传递给需要一个字节的方法时,我们必须执行强制转换操作。
Java 数字提升
数值提升是隐式类型转换的特定类型。 它发生在算术表达式中。 数字提升用于将数字运算符的操作数转换为通用类型,以便可以执行操作。
int x = 3;
double y = 2.5;
double z = x + y;
第三行中有一个加法表达式。 x
操作数为int
,y
操作数为double
。 编译器将整数转换为双精度值,然后将两个数字相加。 结果是两倍。 这是隐式扩展原始类型转换的情况。
byte a = 120;
a = a + 1; // compilation error
此代码导致编译时错误。 在第二行的右侧,我们有一个字节变量a
和一个整数字面值 1。该变量将转换为整数并添加值。 结果是一个整数。 以后,编译器尝试将值分配给a
变量。 没有显式的强制转换运算符,就不可能将较大的类型分配给较小的类型。 因此,我们收到一个编译时错误。
byte a = 120;
a = (byte) (a + 1);
此代码可以编译。 请注意a + 1
表达式中使用圆括号。 (byte)
强制转换运算符的优先级高于加法运算符。 如果要对整个表达式应用转换,则必须使用圆括号。
byte a = 120;
a += 5;
复合运算符自动执行隐式转换。
short r = 21;
short s = (short) -r;
将+
或-
一元运算符应用于变量,即可执行一元数提升。 short
类型升级为int
类型。 因此,必须使用强制转换运算符来使分配通过。
byte u = 100;
byte v = u++;
如果是一元递增++
或递减--
运算符,则不会进行任何转换。 不需要铸造。
Java 装箱,拆箱转换
装箱转换将原始类型的表达式转换为包装器类型的对应表达式。 拆箱转换将包装器类型的表达式转换为原始类型的相应表达式。 从boolean
到Boolean
或从字节到Byte
的转换是装箱转换的示例。 反向转换,例如从Boolean
到boolean
或从Byte
到byte
的翻译是取消装箱转换的示例。
Byte b = 124;
byte c = b;
在第一行代码中,自动装箱转换由 Java 编译器执行。 在第二行中,完成了拆箱转换。
private static String checkAge(Short age) {
...
}
String r = checkAge((short) 5);
在这里,我们在方法调用的上下文中进行装箱转换。 我们将short
类型传递给需要Short
包装类型的方法。 该值已装箱。
Boolean gameOver = new Boolean("true");
if (gameOver) {
System.out.println("The game is over");
}
这是拆箱转换的示例。 在if
表达式内部,调用booleanValue()
方法。 该方法返回Boolean
对象的值作为boolean
原语。
对象引用转换
对象,接口和数组是引用数据类型。 任何引用都可以转换为Object
。 对象类型确定在运行时使用哪种方法。 引用类型确定在编译时将使用哪种重载方法。
接口类型只能转换为接口类型或Object
。 如果新类型是接口,则它必须是旧类型的超级接口。 可以将类类型转换为类类型或接口类型。 如果要转换为类类型,则新类型必须是旧类型的超类。 如果要转换为接口类型,则旧类必须实现该接口。 数组可以转换为类Object
,接口Cloneable
或Serializable
或数组。
引用变量转换有两种类型:下播和上播。 正在向上转换(泛型或扩展)正在从子类型转换为父类型。 我们正在将单个类型转换为通用类型。 向下转换(专业化或缩小)正在从父类型转换为子类型。 我们正在将通用类型转换为单个类型。
向上转换缩小了对象可用的方法和属性的列表,向下转换可以扩展它。 向上转换是安全的,但是向下转换涉及类型检查,并且可能抛出ClassCastException
。
com/zetcode/ReferenceTypeConverion.java
package com.zetcode;
import java.util.Random;
class Animal {}
class Mammal extends Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
public class ReferenceTypeConversion {
public static void main(String[] args) {
// upcasting
Animal animal = new Dog();
System.out.println(animal);
// ClassCastException
// Mammal mammal = (Mammal) new Animal();
var returned = getRandomAnimal();
if (returned instanceof Cat) {
Cat cat = (Cat) returned;
System.out.println(cat);
} else if (returned instanceof Dog) {
Dog dog = (Dog) returned;
System.out.println(dog);
} else if (returned instanceof Mammal) {
Mammal mammal = (Mammal) returned;
System.out.println(mammal);
} else {
Animal animal2 = returned;
System.out.println(animal2);
}
}
private static Animal getRandomAnimal() {
int val = new Random().nextInt(4) + 1;
Animal anim = switch (val) {
case 2 -> new Mammal();
case 3 -> new Dog();
case 4 -> new Cat();
default -> new Animal();
};
return anim;
}
}
该示例执行引用类型转换。
// upcasting
Animal animal = new Dog();
System.out.println(animal);
我们从子类型Dog
转换为父类型Animal
。 这是不安全的,并且始终是安全的。
// ClassCastException
// Mammal mammal = (Mammal) new Animal();
从Animal
向下广播到Mammal
会导致ClassCastException
。
var returned = getRandomAnimal();
if (returned instanceof Cat) {
Cat cat = (Cat) returned;
System.out.println(cat);
} else if (returned instanceof Dog) {
Dog dog = (Dog) returned;
System.out.println(dog);
} else if (returned instanceof Mammal) {
Mammal mammal = (Mammal) returned;
System.out.println(mammal);
} else {
Animal animal2 = returned;
System.out.println(animal2);
}
为了执行合法的向下转换,我们需要首先使用instanceof
运算符检查对象的类型。
private static Animal getRandomAnimal() {
int val = new Random().nextInt(4) + 1;
Animal anim = switch (val) {
case 2 -> new Mammal();
case 3 -> new Dog();
case 4 -> new Cat();
default -> new Animal();
};
return anim;
}
getRandomAnimal()
使用 Java 的switch
表达式返回随机动物。
Java 字符串转换
在数字和字符串之间执行字符串转换在编程中非常常见。 不允许进行强制转换操作,因为字符串和基本类型在根本上是不同的类型。 有几种执行字符串转换的方法。 +
运算符还具有自动字符串转换功能。
本教程的“字符串”一章将介绍有关字符串转换的更多信息。
String s = (String) 15; // compilation error
int i = (int) "25"; // compilation error
不能在数字和字符串之间进行强制转换。 相反,我们有各种方法可以在数字和字符串之间进行转换。
short age = Short.parseShort("35");
int salary = Integer.parseInt("2400");
float height = Float.parseFloat("172.34");
double weight = Double.parseDouble("55.6");
包装类的parse
方法将字符串转换为原始类型。
Short age = Short.valueOf("35");
Integer salary = Integer.valueOf("2400");
Float height = Float.valueOf("172.34");
Double weight = Double.valueOf("55.6");
valueOf()
方法从原始类型返回包装器类。
int age = 17;
double weight = 55.3;
String v1 = String.valueOf(age);
String v2 = String.valueOf(weight);
String
类具有用于将各种类型转换为字符串的valueOf()
方法。
当使用+
运算符并且一个运算符是一个字符串,另一个运算符不是一个字符串时,会自动进行字符串转换。 +
的非字符串操作数将转换为字符串。
com/zetcode/AutomaticStringConversion.java
package com.zetcode;
public class AutomaticStringConversion {
public static void main(String[] args) {
String name = "Jane";
short age = 17;
System.out.println(name + " is " + age + " years old.\n");
}
}
在示例中,我们有String
数据类型和short
数据类型。 使用+
运算符将这两种类型连接成一个句子。
System.out.println(name + " is " + age + " years old.");
在表达式中,age
变量被转换为String
类型。
$ java AutomaticStringConversion.java
Jane is 17 years old.
这是示例输出。
在 Java 教程的这一部分中,我们介绍了包装器类,装箱和拆箱,默认值,转换和促销。
ImageIcon
教程
原文:http://zetcode.com/java/imageicon/
在本教程中,我们将使用ImageIcon
。 我们将绘制一个图标,缩放一个图标,创建一个自定义图标,并将图标放入各种 Swing 组件中。
ImageIcon
Icon
是固定大小的小图片,通常用于装饰组件。 ImageIcon
是Icon
接口的实现,可从图像绘制图标。 可以从 URL,文件名或字节数组创建图像。
paintIcon(Component c, Graphics g, int x, int y)
Icon
的paintIcon()
方法在指定位置绘制图标。
ImageIcon
构造器
ImageIcon
具有多个构造器,包括:
ImageIcon(byte[] imageData)
- 从字节数组创建ImageIcon
。ImageIcon(Image image
) — 从图像对象创建ImageIcon
。ImageIcon(String filename)
- 创建一个ImageIcon
指定的文件。ImageIcon(URL location)
- 从指定的 URL 创建一个ImageIcon
。
ImageIcon
可以处理 PNG,JPEG 和 GIF 图像。 如果要使用 BMP 或 ICO 图像,可以使用 image4j 库。
绘制图标
在第一个示例中,我们将在面板上绘制一个图标。
PaintingIconEx.java
package com.zetcode;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
class DrawingPanel extends JPanel {
private ImageIcon icon;
public DrawingPanel() {
loadImage();
initPanel();
}
private void loadImage() {
icon = new ImageIcon("book.jpg");
}
private void initPanel() {
int w = icon.getIconWidth();
int h = icon.getIconHeight();
setPreferredSize(new Dimension(w, h));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
icon.paintIcon(this, g, 0, 0);
}
}
public class PaintingIconEx extends JFrame {
public PaintingIconEx() {
initUI();
}
private void initUI() {
DrawingPanel dpnl = new DrawingPanel();
createLayout(dpnl);
setTitle("Image");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame ex = new PaintingIconEx();
ex.setVisible(true);
});
}
}
该示例将文件系统中的图像加载到ImageIcon
中并将其绘制在JPanel
组件上。
private void loadImage() {
icon = new ImageIcon("book.jpg");
}
我们将 JPG 图像加载到ImageIcon
中。 该图像位于项目根目录中。
private void initPanel() {
int w = icon.getIconWidth();
int h = icon.getIconHeight();
setPreferredSize(new Dimension(w, h));
}
在initPanel()
方法中,我们使用getIconWidth()
和getIconHeight()
方法确定图标的宽度和高度。 我们设置面板的首选大小以匹配图标大小。
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
icon.paintIcon(this, g, 0, 0);
}
在paintComponent()
方法中,我们使用paintIcon()
方法在面板上绘制图标。
图:绘画图标
缩放图像
以下示例显示了缩放图像的简单方法。
ImageIconScaleEx.java
package com.zetcode;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Image;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
public class ImageIconScaleEx extends JFrame {
public ImageIconScaleEx() {
initUI();
}
private void initUI() {
ImageIcon originalIcon = new ImageIcon("slovakia.png");
JLabel originalLabel = new JLabel(originalIcon);
int width = originalIcon.getIconWidth() / 2;
int height = originalIcon.getIconHeight() / 2;
Image scaled = scaleImage(originalIcon.getImage(), width, height);
ImageIcon scaledIcon = new ImageIcon(scaled);
JLabel newLabel = new JLabel(scaledIcon);
createLayout(originalLabel, newLabel);
setTitle("Scaled icon");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private Image scaleImage(Image image, int w, int h) {
Image scaled = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
return scaled;
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addComponent(arg[1])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ImageIconScaleEx ex = new ImageIconScaleEx();
ex.setVisible(true);
});
}
}
窗口上显示了两个图像:原始图像及其旁边的缩放图像。
ImageIcon originalIcon = new ImageIcon("slovakia.png");
我们将 PNG 图像读取到ImageIcon
中。 该图像位于项目根目录中。
int width = originalIcon.getIconWidth() / 2;
int height = originalIcon.getIconHeight() / 2;
我们使用getIconWidth()
和getIconHeight()
方法获得原始图像的宽度和高度。
Image scaled = scaleImage(originalIcon.getImage(), width, height);
我们将图标的Image
,其width
和height
传递给scaleImage()
方法,在其中执行缩放操作。
private Image scaleImage(Image image, int w, int h) {
Image scaled = image.getScaledInstance(w, h, Image.SCALE_SMOOTH);
return scaled;
}
getScaledInstance()
创建Image
的缩放版本。 我们使用Image.SCALE_SMOOTH
缩放操作,该操作对图像平滑度的优先级高于缩放速度。
ImageIcon scaledIcon = new ImageIcon(scaled);
JLabel newLabel = new JLabel(scaledIcon);
我们从Image
创建一个ImageIcon
,并将其传递给JLabel
组件。
图:缩放 image
自定义图标
Swing 绘画 API 也可以用于创建自定义图标。 图形上下文将传递给paintIcon()
方法。
CustomIconEx.java
package com.zetcode;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
class MissingIcon implements Icon {
private final int WIDTH = 32;
private final int HEIGHT = 32;
private final BasicStroke stroke = new BasicStroke(5);
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
doDrawing(g, x, y);
}
public void doDrawing(Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.white);
g2d.fillRect(x + 1, y + 1, WIDTH - 2, HEIGHT - 2);
g2d.setColor(Color.darkGray);
g2d.drawRect(x + 1, y + 1, WIDTH - 2, HEIGHT - 2);
g2d.setColor(Color.red);
g2d.setStroke(stroke);
g2d.drawLine(x + 10, y + 10, x + WIDTH - 10, y + HEIGHT - 10);
g2d.drawLine(x + 10, y + HEIGHT - 10, x + WIDTH - 10, y + 10);
g2d.dispose();
}
@Override
public int getIconWidth() {
return WIDTH;
}
@Override
public int getIconHeight() {
return HEIGHT;
}
}
class MyLabel extends JLabel {
public MyLabel(Icon icon) {
super(icon);
}
@Override
public boolean isOpaque() {
return true;
}
}
public class CustomIconEx extends JFrame {
public CustomIconEx() {
initUI();
}
private void initUI() {
JLabel lbl = new MyLabel(new MissingIcon());
lbl.setBackground(Color.gray);
add(lbl);
setSize(250, 150);
setTitle("Custom icon");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
CustomIconEx ex = new CustomIconEx();
ex.setVisible(true);
});
}
}
该示例创建一个缺少的自定义图标,并在带有JLabel
的窗口上显示该图标。
class MissingIcon implements Icon {
要创建自定义图标,我们实现Icon
接口。
@Override
public int getIconWidth() {
return WIDTH;
}
@Override
public int getIconHeight() {
return HEIGHT;
}
我们重写getIconWidth()
和getIconHeight()
方法,它们确定图标的大小。
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
doDrawing(g, x, y);
}
我们覆盖了paintIcon()
方法,在该方法中绘制了图标。 Graphics
对象提供了许多绘制 2D 形状并获取有关应用图形环境的信息的方法。
public void doDrawing(Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.white);
g2d.fillRect(x + 1, y + 1, WIDTH - 2, HEIGHT - 2);
g2d.setColor(Color.darkGray);
g2d.drawRect(x + 1, y + 1, WIDTH - 2, HEIGHT - 2);
g2d.setColor(Color.red);
g2d.setStroke(stroke);
g2d.drawLine(x + 10, y + 10, x + WIDTH - 10, y + HEIGHT - 10);
g2d.drawLine(x + 10, y + HEIGHT - 10, x + WIDTH - 10, y + 10);
g2d.dispose();
}
在doDrawing()
方法内部,我们绘制了图标。 该过程与paintComponent()
方法中的绘制相同。 Graphics2D
类扩展了Graphics
类,以提供对几何,坐标转换,颜色管理和文本布局的更复杂的控制。
class MyLabel extends JLabel {
public MyLabel(Icon icon) {
super(icon);
}
@Override
public boolean isOpaque() {
return true;
}
}
我们有一个自定义的MyLabel
组件。 我们将其设为不透明,即标签具有背景。
JLabel lbl = new MyLabel(new MissingIcon());
图标设置为标签组件。
图:缺少自定义图标
ImageIcon
按钮
可以将ImageIcons
放置在JButton
组件上。
ImageIconButtonsEx.java
package com.zetcode;
import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class ImageIconButtonsEx extends JFrame {
public ImageIconButtonsEx() {
initUI();
}
private void initUI() {
ImageIcon quitIcon = new ImageIcon("quit.png");
ImageIcon saveIcon = new ImageIcon("save.png");
ImageIcon homeIcon = new ImageIcon("home.png");
JButton quitBtn = new JButton(quitIcon);
JButton saveBtn = new JButton(saveIcon);
JButton homeBtn = new JButton(homeIcon);
createLayout(quitBtn, saveBtn, homeBtn);
setTitle("JButtons");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2])
);
gl.linkSize(arg[0], arg[1], arg[2]);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ImageIconButtonsEx ex = new ImageIconButtonsEx();
ex.setVisible(true);
});
}
}
我们有三个按钮。 它们每个都显示一个ImageIcon
。
ImageIcon quitIcon = new ImageIcon("quit.png");
ImageIcon saveIcon = new ImageIcon("save.png");
ImageIcon homeIcon = new ImageIcon("home.png");
创建了三个ImageIcons
。 我们将文件名传递给每个构造器。 PNG 文件位于项目根目录中。
JButton quitBtn = new JButton(quitIcon);
JButton saveBtn = new JButton(saveIcon);
JButton homeBtn = new JButton(homeIcon);
JButton
组件接受ImageIcon
作为参数。
图:图像 buttons
JFrame
图标
JFrame
组件可以在其标题栏中显示一个图标。 它显示在标题栏的左侧。
FrameIconEx.java
package com.zetcode;
import java.awt.EventQueue;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
public class FrameIconEx extends JFrame {
public FrameIconEx() {
initUI();
}
private void initUI() {
ImageIcon webIcon = new ImageIcon("web.png");
setIconImage(webIcon.getImage());
setTitle("Icon");
setSize(300, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
FrameIconEx ex = new FrameIconEx();
ex.setVisible(true);
});
}
}
web.png
是一个很小的22x22px
图像文件。
ImageIcon webIcon = new ImageIcon("web.png");
我们从位于项目根目录中的 PNG 文件创建一个ImageIcon
。
setIconImage(webIcon.getImage());
setIconImage()
设置要显示为该窗口图标的图像。 getImage()
返回图标的Image
。
图:图标
JLabel
中的ImageIcon
在下面的示例中,我们将ImageIcons
放入JLabel
组件中。
ImageIconLabelEx.java
package com.zetcode;
import java.awt.Container;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
public class ImageIconLabelEx extends JFrame {
public ImageIconLabelEx() {
initUI();
}
private void initUI() {
JLabel lbl1 = new JLabel(new ImageIcon("cpu.png"));
JLabel lbl2 = new JLabel(new ImageIcon("drive.png"));
JLabel lbl3 = new JLabel(new ImageIcon("laptop.png"));
JLabel lbl4 = new JLabel(new ImageIcon("player.png"));
JLabel lbl5 = new JLabel(new ImageIcon("pda.png"));
createLayout(lbl1, lbl2, lbl3, lbl4, lbl5);
setTitle("Icons");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2])
.addComponent(arg[3])
.addComponent(arg[4])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
.addComponent(arg[1])
.addComponent(arg[2])
.addComponent(arg[3])
.addComponent(arg[4])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ImageIconLabelEx ex = new ImageIconLabelEx();
ex.setVisible(true);
});
}
}
项目根目录中有五个 PNG 文件。 它们显示在JLabel
组件的窗口中。
JLabel lbl1 = new JLabel(new ImageIcon("cpu.png"));
JLabel lbl2 = new JLabel(new ImageIcon("drive.png"));
JLabel lbl3 = new JLabel(new ImageIcon("laptop.png"));
JLabel lbl4 = new JLabel(new ImageIcon("player.png"));
JLabel lbl5 = new JLabel(new ImageIcon("pda.png"));
JLabel
具有一个构造器,该构造器将ImageIcon
作为参数。
图:图标s in labels
JTabbedPane
中的ImageIcon
JTabbedPane
是 Swing 组件,允许用户通过单击选项卡在一组组件之间切换。 这些选项卡可以包含ImageIcons
。
ImageIconTabbedPaneEx
package com.zetcode;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import javax.swing.GroupLayout;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
public class ImageIconTabbedPaneEx extends JFrame {
public ImageIconTabbedPaneEx() {
initUI();
}
private void initUI() {
ImageIcon icon1 = new ImageIcon("dot1.png");
ImageIcon icon2 = new ImageIcon("dot2.png");
ImageIcon icon3 = new ImageIcon("dot3.png");
JTabbedPane tbp = new JTabbedPane();
tbp.setPreferredSize(new Dimension(350, 250));
tbp.addTab("", icon1, new JPanel());
tbp.addTab("", icon2, new JPanel());
tbp.addTab("", icon3, new JPanel());
createLayout(tbp);
setTitle("Icons");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
Container pane = getContentPane();
GroupLayout gl = new GroupLayout(pane);
pane.setLayout(gl);
gl.setAutoCreateContainerGaps(true);
gl.setAutoCreateGaps(true);
gl.setHorizontalGroup(gl.createSequentialGroup()
.addComponent(arg[0])
);
gl.setVerticalGroup(gl.createParallelGroup()
.addComponent(arg[0])
);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
ImageIconTabbedPaneEx ex = new ImageIconTabbedPaneEx();
ex.setVisible(true);
});
}
}
该示例在JTabbedPane
组件的选项卡中显示ImageIcons
。
ImageIcon icon1 = new ImageIcon("dot1.png");
ImageIcon
已创建。
JTabbedPane tbp = new JTabbedPane();
JTabbedPane
已创建。
tbp.addTab("", icon1, new JPanel());
addTab()
方法的第二个参数是ImageIcon
。
图:JTabbedPane
图标
本教程专门针对 Java ImageIcon
。 您可能也对相关教程感兴趣:用 Java 读写 ICO 图像, Java Swing 教程, Java 教程,用 Java 显示图像 。
用 Java 复制文件
原文:http://zetcode.com/java/copyfile/
在 Java 复制文件教程中,我们展示了如何使用 Java 复制文件。 我们复制具有内置类的文件,包括File
,FileInputStream
,FileOutputStream
,FileChannel
和Files
。 我们还使用两个第三方库:Apache Commons IO 和 Google Guava。
文件复制是创建一个与现有文件具有相同内容的新文件。 文件移动正在将文件从一个位置传输到另一位置。
要复制的文件称为源文件,而新副本称为目标文件。
bugs 文件
在示例中,我们使用bugs.txt
文件。
bugs.txt
Assasin bug, Avondale spider, Backswimmer,
Bamboo moth, Banana moth, Bed bug,
Black cocroach, Blue moon, Bumble Bee,
Carpenter Bee, Cattle tick, Cave Weta,
Cicada, Cinnibar, Click beetle, Clothes moth,
Codling moth, Centipede, Earwig, Eucalypt longhorn beetle,
Field Grasshopper, Garden slug, Garden soldier,
German cockroach, German wasp, Giant dragonfly,
Giraffe weevil, Grass grub, Grass looper,
Green planthopper, Green house spider, Gum emperor,
Gum leaf skeletoniser, Hornet, Mealybug,
Mites, Mole Cricket, Monarch butterfly,
Mosquito, Silverfish, Wasp,
Water boatman, Winged weta, Wolf spider,
Yellow Jacket, Yellow Admiral
这是一个包含错误名称的简单文件。
使用FileInputStream
和FileOutputStream
复制文件
使用FileInputStream
和FileOutputStream
,我们创建用于读取和写入File
的流。 找不到文件时,将引发FileNotFoundException
。 File
是 Java 中文件或目录的表示。
com/zetcode/CopyFileStream.java
package com.zetcode;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFileStream {
public static void main(String[] args) throws IOException {
var source = new File("src/resources/bugs.txt");
var dest = new File("src/resources/bugs2.txt");
try (var fis = new FileInputStream(source);
var fos = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
}
}
}
本示例使用FileInputStream
,FileOutputStream
和File
复制文件。
try (var fis = new FileInputStream(source);
var fos = new FileOutputStream(dest)) {
我们创建FileInputStream
和FileOutputStream
的实例。 try-with-resources
语句将自动关闭流。
byte[] buffer = new byte[1024];
我们将复制 1024 个字节的文本块。 这样做是为了获得更好的性能。
while ((length = is.read(buffer)) > 0) {
FileInputStream
的read()
方法从输入流中读取指定数量的字节,并将其存储到缓冲区数组中。 它返回读入缓冲区的字节总数,如果由于到达流的末尾而没有更多数据,则返回 -1。
os.write(buffer, 0, length);
FileOutputStream
的write()
方法将存储在缓冲区中的字节写入输出流。 第一个参数是数据,第二个参数是数据中的起始偏移量,最后一个参数是要写入的字节数。
使用Paths
和Files
复制文件
下一个示例与上一个示例相似。 它使用了更现代的 API。
com/zetcode/CopyFileStream2.java
package com.zetcode;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class CopyFileStream2 {
public static void main(String[] args) throws IOException {
var source = Paths.get("src/resources/bugs.txt");
var dest = Paths.get("src/resources/bugs2.txt");
try (var fis = Files.newInputStream(source);
var fos = Files.newOutputStream(dest)) {
byte[] buffer = new byte[1024];
int length;
while ((length = fis.read(buffer)) > 0) {
fos.write(buffer, 0, length);
}
}
}
}
我们使用Paths
和Files
类复制文件。
var source = Paths.get("src/resources/bugs.txt");
var dest = Paths.get("src/resources/bugs2.txt");
从文件中,我们创建Path
对象。
try (var fis = Files.newInputStream(source);
var fos = Files.newOutputStream(dest)) {
流是在Files
类的帮助下创建的。
使用FileChannel
的复制文件
FileChannel
是用于读取,写入,映射和操作文件的通道。 FileChannel
是经典 Java IO 流 API 的替代方法。 它位于java.nio
包中。
RandomAccessFile
支持读取和写入随机访问文件。 随机访问意味着我们可以在文件中的任何位置读取或写入信息。
com/zetcode/CopyFileChannel.java
package com.zetcode;
import java.io.IOException;
import java.io.RandomAccessFile;
public class CopyFileChannel {
public static void main(String[] args) throws IOException {
var source = new RandomAccessFile("src/resources/bugs.txt", "r");
var dest = new RandomAccessFile("src/resources/bugs2.txt", "rw");
try (var sfc = source.getChannel();
var dfc = dest.getChannel()) {
dfc.transferFrom(sfc, 0, sfc.size());
}
}
}
该示例使用FileChannel
复制文本文件。
var source = new RandomAccessFile("src/resources/bugs.txt", "r");
创建读取模式下的随机访问文件。
var dest = new RandomAccessFile("src/resources/bugs2.txt", "rw");
创建一个处于读/写模式的随机访问文件。
try (var sfc = source.getChannel();
var dfc = dest.getChannel()) {
我们使用getChannel()
从文件中获取频道。
dfc.transferFrom(sfc, 0, sfc.size());
transferFrom()
方法将字节从源通道传输到目标通道。 第一个参数是源通道,第二个参数是文件中传输的开始位置,第三个参数是要传输的最大字节数。
用Files.copy
复制文件
Java 7 引入了Files.copy()
方法,该方法提供了一种复制文件的简便方法。 如果目标文件存在,则复制失败,除非指定了REPLACE_EXISTING
选项。 Files.copy()
采用可选的第三副本选项参数。
options
参数可以包括以下任何一项:
REPLACE_EXISTING
- 如果目标文件存在,则目标文件不是非空目录时,将替换目标文件。COPY_ATTRIBUTES
- 尝试将与此文件关联的文件属性复制到目标文件。ATOMIC_MOVE
- 移动文件。NOFOLLOW_LINKS
- 不遵循符号链接。
前三个选项在StandarCopyOption
中可用; LinkOption
中的最后一个。
com/zetcode/CopyFileJava7.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class CopyFileJava7 {
public static void main(String[] args) throws IOException {
var source = new File("src/resources/bugs.txt");
var dest = new File("src/resources/bugs2.txt");
Files.copy(source.toPath(), dest.toPath(),
StandardCopyOption.REPLACE_EXISTING);
}
}
本示例使用Files.copy()
复制文件。 如果目的地已经存在,它将替换目的地。
使用 Apache Commons IO 进行 Java 复制文件
Apache Commons IO 是一个工具库,可帮助开发 IO 功能。 它包含FileUtils.copyFile()
方法来执行复制。 FileUtils.copyFile()
将指定源文件的内容复制到指定目标文件中,并保留文件日期。 如果目标文件所在的目录不存在,则创建该目录。 如果目标文件存在,则此方法将覆盖它。
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.5</version>
</dependency>
对于此示例,我们需要commons-io
工件。
com/zetcode/CopyFileApacheCommons.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
public class CopyFileApacheCommons {
public static void main(String[] args) throws IOException {
var source = new File("src/main/resources/bugs.txt");
var dest = new File("src/main/resources/bugs2.txt");
FileUtils.copyFile(source, dest);
}
}
该示例使用 Apache Commons 的FileUtils.copyFile()
复制文件。
用 Guava Java 复制文件
Google Guava 是 Java 通用库的开源集。 它包括用于复制文件的Files.copy()
。
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>
我们使用 Guava 依赖。
com/zetcode/CopyFileGuava.java
package com.zetcode;
import com.google.common.io.Files;
import java.io.File;
import java.io.IOException;
public class CopyFileGuava {
public static void main(String[] args) throws IOException {
var source = new File("src/main/resources/bugs.txt");
var dest = new File("src/main/resources/bugs2.txt");
Files.copy(source, dest);
}
}
该示例使用 Guava 的Files.copy()
复制文件。
在本教程中,我们展示了几种用 Java 复制文件的方法。 我们使用了内置工具和第三方库。 您可能也对相关教程感兴趣: Java 文件教程, Java 创建目录, Java 文件大小,用 Java 创建文件 ,用 Java 读取文本文件, Apache FileUtils
教程, Java 教程。
列出所有 Java 教程。
Java 文件时间教程
原文:http://zetcode.com/articles/javafiletime/
在 Java 文件时间教程中,我们展示了如何使用Files
和BasicFileAttributes
确定 Java 中的文件创建,最后修改和最后访问时间。
档案
Files
是 Java 类,其中包含对文件,目录或其他类型的文件进行操作的静态方法。 通常,这些方法将委派给关联的文件系统供应器来执行文件操作。
BasicFileAttributes
BasicFileAttributes
保留基本文件属性。 这些是许多文件系统共有的属性,由强制性和可选文件属性组成,例如文件创建时间的大小。 通过Files.readAttributes()
方法检索BasicFileAttributes
。
Java 文件创建时间
使用BasicFileAttributes.creationTime()
方法检索 Java 中的文件创建时间。
JavaFileLastCreationTime.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class JavaFileLastCreationTime {
public static void main(String[] args) throws IOException {
String fileName = "/home/janbodnar/world.sql";
File myfile = new File(fileName);
Path path = myfile.toPath();
BasicFileAttributes fatr = Files.readAttributes(path,
BasicFileAttributes.class);
System.out.printf("File creation time: %s%n", fatr.creationTime());
}
}
本示例打印指定文件的创建时间。
File creation time: 2017-06-01T12:48:40Z
这是一个示例输出。
Java 文件的最后修改时间
BasicFileAttributes.lastModifiedTime()
方法获取 Java 中文件的最后修改时间。
JavaFileLastModifiedTime.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class JavaFileLastModifiedTime {
public static void main(String[] args) throws IOException {
String fileName = "/home/janbodnar/world.sql";
File myfile = new File(fileName);
Path path = myfile.toPath();
BasicFileAttributes fatr = Files.readAttributes(path,
BasicFileAttributes.class);
System.out.printf("Last modification time: %s%n", fatr.lastModifiedTime());
}
}
本示例打印指定文件的最后修改时间。
Last modification time: 2017-06-01T12:48:40Z
This is a sample output.
Java 文件上次访问时间
使用BasicFileAttributes.lastAccessTime()
方法检索 Java 中文件的最后访问时间。
JavaFileLastAccessTime.java
package com.zetcode;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
public class JavaFileLastAccessTime {
public static void main(String[] args) throws IOException {
String fileName = "/home/janbodnar/world.sql";
File myfile = new File(fileName);
Path path = myfile.toPath();
BasicFileAttributes fatr = Files.readAttributes(path,
BasicFileAttributes.class);
System.out.printf("Last access time: %s%n", fatr.lastAccessTime());
}
}
本示例显示指定文件的最后访问时间。
Last access time: 2017-06-01T12:48:40Z
This is a sample output.
在本教程中,我们使用Files
和BasicFileAttributes
确定了文件的创建,最后修改和最后访问时间。
您可能也对以下相关教程感兴趣:Java FileWriter
教程,用 Java 读取文本文件,Java 附加到图块, Java 文件复制,Java 教程,用 Java8 的StringJoiner
连接字符串,Java 读取网页或 Google Guava 简介。
如何使用 Java 获取当前日期时间
原文:http://zetcode.com/articles/javacurrentdatetime/
Java 当前日期时间教程介绍了各种 Java 类,以获取 Java 中的当前日期时间。
有几种方法可以获取 Java 中的当前日期和时间。 Java 程序员可以使用 Java8(java.time
)中引入的现代日期和时间 API,经典的,过时的 API(java.util
)和第三方 Joda 库。
使用java.time
的当前日期和时间
java.time
包包含日期,时间,瞬间和持续时间的主要 API。 它是对过时的java.util
日期和时间 API 的现代替代。
使用Instant
获取当前日期和时间
java.time.Instant
在时间轴上模拟单个瞬时点。 这可用于记录应用中的事件时间戳。
JavaCurrentDateInstant.java
package com.zetcode;
import java.time.Instant;
public class JavaCurrentDateInstant {
public static void main(String[] args) {
Instant instant = Instant.now();
System.out.println(instant);
}
}
该代码示例使用java.time.Instant
获取当前日期和时间。
Instant instant = Instant.now();
Instant.now()
方法从系统时钟获取当前时刻。
使用LocalDateTime
获取当前日期和时间
java.time.LocalDateTime
创建不带时区的日期时间。
JavaCurrentDateLocalDateTime.java
package com.zetcode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class JavaCurrentDateLocalDateTime {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(now));
}
}
该示例使用java.time.LocalDateTime
获取当前日期时间,并使用java.time.format.DateTimeFormatter
对其进行格式化。
LocalDateTime now = LocalDateTime.now();
LocalDateTime.now()
方法从系统时钟以默认时区获取当前日期时间。
使用ZonedDateTime
获取当前日期和时间
java.time.ZonedDateTime
是带有时区的日期时间的不变表示。
JavaCurrentDateTimeZonedDateTime.java
package com.zetcode;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
public class JavaCurrentDateTimeZonedDateTime {
public static void main(String[] args) {
ZonedDateTime now = ZonedDateTime.now();
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
System.out.println(dtf.format(now));
}
}
该示例使用java.time.ZonedDateTime
获取当前日期时间,并使用java.time.format.DateTimeFormatter
对其进行格式化。
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime.now()
方法从系统时钟以默认时区获取当前日期时间。
使用Clock
获取当前日期和时间
java.time.Clock
使用时区提供对当前时刻,日期和时间的访问。
JavaCurrentDateTimeClock.java
package com.zetcode;
import java.time.Clock;
import java.time.Instant;
public class JavaCurrentDateTimeClock {
public static void main(String[] args) {
Clock clock = Clock.systemDefaultZone();
Instant now = clock.instant();
System.out.println(now);
}
}
该示例使用java.time.Clock
获取当前日期时间。
Clock clock = Clock.systemDefaultZone();
Clock.systemDefaultZone()
方法获得一个时钟,该时钟使用最佳可用系统时钟返回当前时刻,并使用默认时区转换为日期和时间。
使用java.util
的当前日期和时间
java.util
(Date
和Calendar
)中可用的类被认为已过时。 这是原始的 Java 日期和时间 API。
使用Date
获取当前日期和时间
java.util.Date
表示特定的时间瞬间,精度为毫秒。
JavaCurrentDateTimeDate.java
package com.zetcode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaCurrentDateTimeDate {
public static void main(String[] args) {
Date now = new Date();
DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
System.out.println(df.format(now));
}
}
该示例使用java.util.Date
获取当前日期时间,并使用java.text.SimpleDateFormat
对其进行格式化。
使用Calendar
获取当前日期和时间
java.util.Calendar
表示特定的时间瞬间,精度为毫秒。
JavaCurrentDateTimeCalendar.java
package com.zetcode;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
public class JavaCurrentDateTimeCalendar {
public static void main(String[] args) {
Date now = Calendar.getInstance().getTime();
DateFormat df = new SimpleDateFormat("yyyy-MM-d HH:mm:ss");
System.out.println(df.format(now));
}
}
该示例使用java.util.Calendar
获取当前日期时间,并使用java.text.SimpleDateFormat
对其进行格式化。
使用 Joda 时间库的当前日期和时间
Joda time 是第三方日期和时间库,用于替换过时的 JDK 日期时间 API。
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.9.9</version>
</dependency>
我们需要joda-time
依赖项。
使用 Joda LocalDateTime
获取当前日期和时间
org.joda.time.LocalDateTime
是不可修改的日期时间类,表示没有时区的日期时间。
JavaCurrentDateTimeJoda.java
package com.zetcode;
import org.joda.time.LocalDateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class JavaCurrentDateTimeJoda {
public static void main(String[] args) {
LocalDateTime ldt = new LocalDateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy, MMMM dd, HH:mm:ss");
String str = fmt.print(ldt);
System.out.println(str);
}
}
该示例使用org.joda.time.LocalDateTime
获取当前日期时间,并使用org.joda.time.format.DateTimeFormatter
对其进行格式化。
使用 Joda DateTime
获取当前日期和时间
org.joda.time.DateTime
是不可修改的日期时间类的标准实现。
JavaCurrentDateTimeJoda2.java
package com.zetcode;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
public class JavaCurrentDateTimeJoda2 {
public static void main(String[] args) {
DateTime dt = DateTime.now();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy, MMMM dd, HH:mm:ss");
String str = fmt.print(dt);
System.out.println(str);
}
}
该示例使用org.joda.time.DateTime
获取当前日期时间,并使用org.joda.time.format.DateTimeFormatter
对其进行格式化。
在本教程中,我们展示了如何获取 Java 中的当前日期和时间。 您可能也对相关教程感兴趣: Java HashSet
教程, Java HashMap
教程, Java 中的HashMap
迭代, Java static
关键字 , Java Swing 教程, Java 教程,用 Java 显示图像。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库