首页 > 编程语言 >ZetCode-Java-教程-二-

ZetCode-Java-教程-二-

时间:2024-10-24 18:10:18浏览次数:1  
标签:教程 Java String java ZetCode new import public

ZetCode Java 教程(二)

原文:ZetCode

协议:CC BY-NC-SA 4.0

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 标识符

标识符是变量,方法,类或参数的名称。 标识符可以包含字母数字字符,下划线和美元符号($)。 以数字开头的变量名称是错误的。 名称中不允许使用空格。

标识符区分大小写。 这意味着NamenameNAME引用了三个不同的变量。 标识符也不能与语言关键字匹配。

还有一些与标识符命名有关的约定。 名称应具有描述性。 我们不应该将隐名用作标识符。 如果名称由多个单词组成,则随后的每个单词均大写。

以下是有效的 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 中, Namename是两个不同的标识符。 而在 Visual Basic 中,他们是两个相同的标识符,因为 Visual Basic 的变量名不区分大小写。

$ java com.zetcode.CaseSensitiveIdentifiers 
Robert
Julia

Java 字面值

字面值是类型的特定值的字面值表示。 字面值类型包括布尔值,整数,浮点数,字符串,空值或字符。 从技术上讲,字面值将在编译时分配一个值,而变量将在运行时分配。

int age = 29;
String nationality = "Hungarian";

在这里,我们为agenationality两个变量分配了两个字面值。 数字 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);
        }
    }
}

packagepublicclassstaticvoidintfor标记是 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。 我们已经介绍了RequestDispatcherforwardinclude()方法。 您可能还需要查看相关的教程: Java servlet JSON 教程从 Java servletJava 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 流的一个很好的比喻是水从水龙头流入浴缸,然后又流入排水装置。 InputStreamOutputStream是对数据(例如 C 文件指针)的低级访问的抽象。

Java InputStream

InputStream是读取数据的源。 流可以表示各种类型的源,包括磁盘文件,设备,其他程序和内存数组。

流支持许多不同类型的数据,包括简单字节,原始数据类型,本地化字符和对象。

Java InputStream子类

InputStream是一个抽象类; 它是表示字节输入流的所有类的超类,包括AudioInputStreamByteArrayInputStreamFileInputStreamFilterInputStreamObjectInputStreamPipedInputStreamSequenceInputStream

Java InputStream关闭

FileInputStreamclose()方法关闭输入流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用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));
        }
    }
}

使用FileInputStreamInputStreamReaderBufferedReader读取文本文件。

try (InputStream fis = new FileInputStream(fileName);

FileInputStreamInputStream的一种特殊形式,用于从文件中读取字节。

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是用于将数据写入FileFileDescriptor的输出流。 FileOutputStreamOutputStream的子类,它接受输出字节并将其发送到某个接收器。 在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关闭

FileOutputStreamclose()方法关闭文件输出流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用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 便利类,它有一个严重的限制:它使用默认编码,并且不允许我们显式指定编码。 如果必须设置编码,则可以使用OutputStreamWriterFileOutputStream

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关闭

FileInputStreamclose()方法关闭文件输入流,并释放与此流关联的所有系统资源。 在我们的示例中,我们使用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

ZipInputStreamgetNextEntry()读取下一个 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);

我们从文件创建一个FileInputStreamFileInputStream用于读取原始字节流。

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 文件的内容,并使用FileOutputStreamBufferedOutputStream将该内容写入目录。

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写入文件

使用FileInputStreamFileOutputStream,我们创建用于读取和写入File的流。 找不到文件时,将引发FileNotFoundExceptionFile是 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");

FileWriterwrite()方法将文本写入文件。

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的第二个参数告诉我们将附加到文件中。

使用BufferedWriterFileWriter

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。 然后,我们调用BufferedWriterwrite()方法来编写文本。

try (BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {

读取操作也通过BufferedReader类进行缓冲。

指定编码

FileWriter使用默认编码,并且不允许我们显式指定编码。 如果必须设置编码,则可以使用OutputStreamWriterFileOutputStream

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

Server and Settings

图:服务器和设置

在此对话框中,我们选择应用服务器,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包,然后选择无状态会话类型。

Creating a new session bean in NetBeans

图:在 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

New servlet

图:创建新的 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 project structure

图: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数据库。

Persistence provider

图:持久性供应器

在此视图中,我们提供了持久性单元名称,持久性供应器,数据源和表生成策略。

实体 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 tool

图:NetBeans Derby 工具

我们可以使用 NetBeans Derby 工具来连接和管理数据库中的数据。 该工具位于“服务”窗口中。

servlet

我们创建两个 servlet:SaveCarReadCar。 我们将它们放入com.zetcode.web包中。 Servlet 是从 NetBeans Web 类别创建的。 两个 servlet 均以纯文本形式响应。

Apache Common Lang JARs

图: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);

创建一个新的汽车对象并将其保存到数据库。 为了保存汽车对象,我们利用了ManageCarBeansaveCar()方法。

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";
}

ManageCarBeanreadCar()方法用于检索汽车对象。

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

Error pages

图:错误页面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 server properties

图: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()方法,我们遍历映射并打印其键/值对。

在下一个示例中,我们在代码中显式显示ConsumerMap.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);
    }
}

在此示例中,我们过滤字符串列表,并将过滤后的列表打印到控制台。 仅显示具有四个字符的字符串。

IntConsumerLongConsumerDoubleConsumer

从 Java8 开始,我们为原始数据类型内置了使用者接口:IntConsumerLongConsumerDoubleConsumer

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构造声明了一个字符串数组。 argsmain()方法的参数。 然后该方法可以使用传递给它的参数。

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。

Database creation

图:数据库创建

在“服务”选项卡中,我们右键单击 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表。 我们右键单击“数据库”节点,然后选择“新建连接”选项。

Connection wizard

图:连接向导

我们在连接向导中填写必要的详细信息。 我们使用 Derby 网络驱动程序; Derby 的端口是 1527。

Connections

图:连接

创建一个新的连接对象; 它由橙色图标表示。 其上下文菜单提供了用于连接到指定数据库并执行命令的选项。 “执行命令”选项显示了执行 SQL 命令的工具。 在此窗口中,我们可以使用上面的 SQL 创建Cars表。

NetBeans Derby tool

图: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/[email protected]/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/[email protected]/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/[email protected]/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);
        }
    }
}

JdbcDAOCarDAO接口的具体实现。 它使用 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()方法中。

工具类

我们创建了三个工具类:ServiceLocatorValidateParameterDBUtils。 这些类位于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()方法中的视图。 这样可以防止提交多个表单。 (例如,我们可能不小心多次添加了汽车)。

Displaying all cars

图:显示所有汽车

在本教程中,我们创建了一个简单的 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.jarcos-05Nov2002.jarstripes-1.6.0.jar。 此外,还有StripesResources.properties文件,其中包含各种消息。

简单 Stripes 应用

第一个应用显示当前日期。 我们在 NetBeans 中创建一个新的 Web 应用。 我们选择 Tomcat 作为我们的 JSP/servlet 硬币容器。

The project files

图:项目文件

该项目包含三个文件:HelloActionBean.java包含响应我们请求的代码,showDate.jsp是作为响应发送回用户的视图,而web.xml文件包含用于设置 Stripes 的配置。 在此应用中,我们不使用StripesResources.properties

The project libraries

图:项目库

这些是我们构建 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/>创建一个文本字段。 创建的请求参数将自动映射到HelloActionBeanuserName属性。

StripesResources.properties

图: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);
    }
}

单击提交按钮时,将执行HelloActionBeanrequest参数自动绑定到其userName属性。 默认处理器将转发到greet.jsp视图。

@Validate(required=true)
private String userName;

@Validate注解用于强制验证表单的用户名字段。 如果未输入任何值,则会显示一条错误消息。

Validation error message

图:验证错误消息

我们应用中的第二个视图是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}表达式,我们获得了用户名。

Greeting

图:问候语

该应用以一条简单消息响应。

在本教程中,我们使用 Stripes Web 框架创建了两个简单的 Web 应用。 您可能也对 ZetCode 的 Java 教程验证过滤器教程Java MVC 教程Play 框架简介Spark JavaStripes,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 一起提供。

Server and Settings

图:项目库

在“NetBeans 服务”选项卡中,我们展开“数据库”节点,然后右键单击 Java DB 节点,然后选择“创建数据库”选项。 数据库名称将为books,用户名和密码为appapp

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目录。 在源类别中,我们添加一个新的源包文件夹。

Resources

图:资源

BookMapper.xmlmybatis-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:AddBookActionBeanSelectAllBooksActionBeanSelectOneBookActionBean。 它们每个代表在应用中要执行的某些操作。

Action beans

图:动作 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 字段提供了验证服务。 这些字段不能为空,并且必须与正确的数据类型匹配。

Validation

图:验证

如果发布年份具有非整数字符,则“提交”操作将失败。

@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;
    }
}

MyBatisDAOBookDAO接口的具体实现。 如果基础数据源发生更改,我们可以轻松地切换到新的 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>

注意,对于结果类型,我们不定义集合,而是定义集合项的类型。

Displaying all books

图:显示所有书籍

屏幕截图显示了从示例数据库中选择的所有书籍。

在本教程中,我们使用 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 是来自 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是管理实体的持久状态的类。

第一个示例是 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文件中,我们定义了两个依赖项:eclipselinkderbyclient

Creating persistence.xml file

图:创建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 文件,该文件将数据填充到表中。

The persistence.xml file

图: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,构建应用并执行它之后,我们得到此输出。

在第二个示例中,我们将一个新的汽车对象保存到数据库中。 pom.xmlpersistence.xmlCar.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 应用中。 Spring 是流行的 Java 应用框架。 Spring Boot 是 Spring 的解决方案,用于创建独立的,生产级的基于 Spring 的应用。 这是一个新的解决方案,可以以最少的工作量创建 Spring 应用。

以下示例的资源也可从作者的 Github 仓库中获得。

Spring Boot project

图: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:EntityManagerFactoryEntityManagerEntityManager将被注入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()方法通过EntityManagerpersist()方法将汽车保存到数据库中。 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 应用连接到数据库。

Project structure

图:项目结构

这就是 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 上。

Project libraries

图:项目库

在我们的项目中,我们使用 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

我们部署应用。

Displaying cars

图:展示汽车

我们在浏览器中导航到该应用,然后从数据库中获取数据。

这是 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 上。

Project structure

图:结构

该图显示了 NetBeans 中的项目结构。 我们有三个 JSP 页面,两个 J​​ava 类和两个 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页面。

Error message

图:错误消息

如果电子邮件格式不正确,则应用将以错误消息响应。

这是验证过滤器教程。 我们已经使用 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 仓库中获得。

Project structure

图:项目结构

该图显示了 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.jspvalError.jsp页面。

Error message

图:错误消息

如果电子邮件格式不正确,则应用将以错误消息响应。

这是验证过滤器教程。 我们使用 Hibernate Validator,JSTL,JSP,Apache Tomcat 和 Maven 构建了一个控制台和一个 Web 应用。 您可能还需要查看一些相关的教程: Java 验证过滤器教程Java 教程Stripes 教程

用 Java 显示图像

原文:http://zetcode.com/java/displayimage/

在 Java 中显示图像教程介绍了如何在 Java 中显示图像。 我们展示了如何使用命令行工具 Ant,Maven,NetBeans 和 Eclipse 构建项目。 源代码和图像可在作者的 Github 仓库中获得。

初学者程序员经常在项目中显示图像时遇到问题。 问题在于正确识别图像文件的路径。 关键部分是要认识到图像文件的相对路径是从项目目录开始的。 创建本教程的目的是使事情变得清楚。

以下示例显示了该应用的屏幕截图。

Displaying image in Java

图:用 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 应用。

我们创建一个新文件夹。 我们右键单击“源包”,然后选择“新建—文件夹”。

Creating a folder in NetBeans

图:在 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目录是项目目录的子目录。

Project in NetBeans

图:项目结构 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路径。

Project in Eclipse

图:项目结构 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命令运行该应用。 该应用中已经包含一些简单的代码。

First Play application

图:第一个 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 请求转换为操作调用的组件,该操作调用是控制器类中的公共方法。 我们可以看到根路由/调用了HomeControllerindex()方法。

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>

这是主模板,从索引模板调用。 它用字符串数据填充标题和内容。

First Play application 2

图:第一个 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数据类型是具有以下两个值之一的原始数据类型:truefalse

快乐的父母正在等待孩子的出生。 他们为两种可能性都选择了名称。 如果要成为男孩,他们选择了罗伯特。 如果要成为女孩,他们会选择维多利亚。

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 字母Ll,则其类型为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类型的整数字面值。 byteshort类型没有整数字面值。 如果这些值适合目标类型,则编译器不会抱怨并自动执行转换。 对于小于Integer.MAX_VALUElong数字,L后缀是可选的。

long x = 2147483648L;
long y = 2147483649L;

对于大于Integer.MAX_VALUElong数字,我们必须添加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 开始,可以用下划线分隔整数。

下划线不能在数字的开头或结尾,浮点字面量中的小数点附近以及FL后缀之前使用。

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;

我们有两个相同的长数字。 在第二个数字中,我们将数字中的每三个数字分开。 比较这两个数字,我们得到一个布尔值trueL后缀告诉编译器我们有一个长整数。

使用的 Java byteshortintlong类型确实表示固定精度数字。 这意味着它们可以表示有限数量的整数。 长类型可以代表的最大整数是 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 中,我们有两种原始浮点类型:floatdoublefloat是单精度类型,以 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

这是程序的输出。 小数舍入误差不会影响我们对短跑运动员速度的理解。

floatdouble类型不精确。

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 project structure

图: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是一个抽象类,具有两个子类:PropertyResourceBundleListResourceBundlePropertyResourceBundle从属性文件加载数据。 属性文件是包含可翻译文本的纯文本文件。 属性文件不是 Java 源代码的一部分,它们只能包含String值。 ListResourceBundle通过方便的列表管理资源; 它从类文件中获取数据。 我们可以将任何特定于语言环境的对象存储在ListResourceBundle中。

为了获得适当的ResourceBundle,我们调用ResourceBundle.getBundle()方法。 这是一种寻找ListResourceBundle的工厂方法,如果找不到,将寻找PropertyResourceBundle。 如果找不到资源束,则抛出MissingResourceException

Java PropertyResourceBundle示例

在第一个应用中,我们创建一个简单的 Java 应用,该应用使用三个资源包:默认的英语,德语和斯洛伐克语。

NetBeans project structure

图: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 project structure II

图: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 application

图:Swing 应用

Spring Boot 应用

在下一个示例中,我们在 Spring Boot 应用中使用资源包。 Spring 是流行的 Java 应用框架。 Spring Boot 是一种新的解决方案,可以轻松创建基于生产级别的独立 Spring 应用。

NetBeans project structure of a Spring Boot application

图: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-coreslf4j-simple依赖项指定仓库。

变量

在第一个应用中,我们探索 Jtwig set命令。 set命令允许在 Jtwig 模板中指定分配操作。 它将表达式的结果分配给指定的变量。

Java console project structure in NetBeans

图: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-coreslf4j-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命令,我们可以包含其他模板文件。

Java console project structure in NetBeans 2

图: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.twighead.twig模板。

Header.

Main content.

Footer.

This is the output of the example.

Jtwig servlet 示例

在下面的示例中,我们在标准 Java Web 应用中使用 Jtwig。 该应用打包到war文件中,并部署在 NetBeans 的内置 Tomcat 服务器上。

在 NetBeans 中,我们创建一个新的 Maven Web 应用。

Jtwig servlet project structure in NetBeans

图: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 example

图:Jtwig servlet 示例

我们在 Opera 网络浏览器中显示应用输出。 NetBeans 中的内置 Tomcat 在 8084 端口上运行。

Spring Boot

Spring 是流行的 Java 应用框架。 Spring Boot 是通过最小的努力来创建独立的,生产级的基于 Spring 的应用的产物。

Spring Boot 命令行应用

在下一个应用中,我们将 Jtwig 集成到 Spring Boot 命令行应用中。 它是放置在 Spring Boot 框架中的控制台应用。

Spring Boot project structure in NetBeans

图: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-starterspring-webjtwig-spring-boot-starterslf4j-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 example

图: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.servletjavax.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()方法。 该方法接收HttpServletRequestHttpServletResponse对象。

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。

SocketServerSocket用于 TCP 协议。 DatagramSocketDatagramPacket用于 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 用于管理应用。

目录

  1. FreeMarker 模板引擎
  2. 控制台应用
  3. FreeMarker 指令
  4. Servlet 和 FreeMarker
  5. Spark 和 FreeMarker
  6. Spring 和 FreeMarker

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 模板文件来生成简单的文本输出。

Java console project structure in NetBeans

图: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 应用。

FreeMarker servlet project structure in NetBeans

图: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 example

图: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:freemarkerConfigviewResolver。 这些是 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 的请求到达时被调用)中,我们调用VersionServicegetVersion()方法并将该值传递到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 应用中。

Spring Boot web project structure in NetBeans

图: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 example

图: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 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);

使用Jsoupparse()方法,我们解析 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)
        )
);

我们使用新字体创建图表标题。

Line chart with JFreeChart

图:折线图

带有两个序列的折线图

在第二个示例中,我们创建具有两个数据序列的折线图。

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);

网格线已关闭。

Line chart with two series

图:具有两个系列的折线图

将图表保存到图像

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);

我们调整图表的结尾。

Area chart

图:面积图

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()方法创建条形图。

Bar chart

图:条形图

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()将二进制数据写入输出字符串。

A pie chart in a browser

图:浏览器中的饼图

显示来自 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 byteString
short Short shortString
int Integer intString
long Long longString
float Float floatdoubleString
double Double doubleString
char Char char
boolean Boolean booleanString

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 装箱

从原始类型转换为对象类型称为boxingUnboxing是相反的操作。 它正在将对象类型转换回原始类型。

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表达式的方括号内,将Integerint进行比较。 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 之间的任何shortint),以及这两个值之一的任意装箱转换可以确保得到相同的对象。

根据 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运算符,则将创建两个不同的对象,并且==运算符将返回falseequals()方法在数值上比较两个Integer对象。 它返回布尔值truefalse(在我们的例子中为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 是整数字面值,ab变量为byteshort类型。 可以使用铸造操作,但不是必需的。 字面值可以在赋值左侧的变量中表示。 我们处理隐式变窄转换。

private static byte calc(byte x) {
...
}
byte b = calc((byte) 5);

以上规则仅适用于分配。 当我们将整数字面值传递给需要一个字节的方法时,我们必须执行强制转换操作。

Java 数字提升

数值提升是隐式类型转换的特定类型。 它发生在算术表达式中。 数字提升用于将数字运算符的操作数转换为通用类型,以便可以执行操作。

int x = 3;
double y = 2.5;
double z = x + y;

第三行中有一个加法表达式。 x操作数为inty操作数为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 装箱,拆箱转换

装箱转换将原始类型的表达式转换为包装器类型的对应表达式。 拆箱转换将包装器类型的表达式转换为原始类型的相应表达式。 从booleanBoolean或从字节到Byte的转换是装箱转换的示例。 反向转换,例如从Booleanboolean或从Bytebyte的翻译是取消装箱转换的示例。

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,接口CloneableSerializable或数组。

引用变量转换有两种类型:下播和上播。 正在向上转换(泛型或扩展)正在从子类型转换为父类型。 我们正在将单个类型转换为通用类型。 向下转换(专业化或缩小)正在从父类型转换为子类型。 我们正在将通用类型转换为单个类型。

向上转换缩小了对象可用的方法和属性的列表,向下转换可以扩展它。 向上转换是安全的,但是向下转换涉及类型检查,并且可能抛出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是固定大小的小图片,通常用于装饰组件。 ImageIconIcon接口的实现,可从图像绘制图标。 可以从 URL,文件名或字节数组创建图像。

paintIcon(Component c, Graphics g, int x, int y)

IconpaintIcon()方法在指定位置绘制图标。

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()方法在面板上绘制图标。

Painting icon

图:绘画图标

缩放图像

以下示例显示了缩放图像的简单方法。

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,其widthheight传递给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组件。

Scaling image

图:缩放 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());

图标设置为标签组件。

Missing custom icon

图:缺少自定义图标

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作为参数。

Image buttons

图:图像 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

Icon

图:图标

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作为参数。

Icons in labels

图:图标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 icons

图:JTabbedPane图标

本教程专门针对 Java ImageIcon。 您可能也对相关教程感兴趣:用 Java 读写 ICO 图像Java Swing 教程Java 教程用 Java 显示图像

用 Java 复制文件

原文:http://zetcode.com/java/copyfile/

在 Java 复制文件教程中,我们展示了如何使用 Java 复制文件。 我们复制具有内置类的文件,包括FileFileInputStreamFileOutputStreamFileChannelFiles。 我们还使用两个第三方库: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

这是一个包含错误名称的简单文件。

使用FileInputStreamFileOutputStream复制文件

使用FileInputStreamFileOutputStream,我们创建用于读取和写入File的流。 找不到文件时,将引发FileNotFoundExceptionFile是 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);
            }
        }
    }
}

本示例使用FileInputStreamFileOutputStreamFile复制文件。

try (var fis = new FileInputStream(source);
     var fos = new FileOutputStream(dest)) {

我们创建FileInputStreamFileOutputStream的实例。 try-with-resources语句将自动关闭流。

byte[] buffer = new byte[1024];

我们将复制 1024 个字节的文本块。 这样做是为了获得更好的性能。

while ((length = is.read(buffer)) > 0) {

FileInputStreamread()方法从输入流中读取指定数量的字节,并将其存储到缓冲区数组中。 它返回读入缓冲区的字节总数,如果由于到达流的末尾而没有更多数据,则返回 -1。

os.write(buffer, 0, length);

FileOutputStreamwrite()方法将存储在缓冲区中的字节写入输出流。 第一个参数是数据,第二个参数是数据中的起始偏移量,最后一个参数是要写入的字节数。

使用PathsFiles复制文件

下一个示例与上一个示例相似。 它使用了更现代的 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);
            }
        }
    }
}

我们使用PathsFiles类复制文件。

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 文件时间教程中,我们展示了如何使用FilesBasicFileAttributes确定 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.

在本教程中,我们使用FilesBasicFileAttributes确定了文件的创建,最后修改和最后访问时间。

您可能也对以下相关教程感兴趣: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.utilDateCalendar)中可用的类被认为已过时。 这是原始的 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 显示图像

标签:教程,Java,String,java,ZetCode,new,import,public
From: https://www.cnblogs.com/apachecn/p/18500142

相关文章

  • ZetCode-GUI-教程-九-
    ZetCodeGUI教程(九)原文:ZetCode协议:CCBY-NC-SA4.0wxWidgets中的布局管理原文:http://zetcode.com/gui/wxwidgets/layoutmanagement/典型的应用由各种小部件组成。这些小部件放置在容器小部件内。程序员必须管理应用的布局。这不是一件容易的事。在wxWidgets中,我......
  • ZetCode-PHP-教程-一-
    ZetCodePHP教程(一)原文:ZetCode协议:CCBY-NC-SA4.0PHP教程原文:https://zetcode.com/lang/php/这是PHP教程。本教程涵盖了PHP编程语言的核心。它使用PHPCLI。PHP教程适合初学者。目录PHP语言词法结构基础知识数据类型字符串运算符控制流数组数组......
  • 计算机毕业设计项目推荐:大学生实习成绩评价系统的设计与实现38147(开题答辩+程序定制+
    摘 要21世纪的今天,随着社会的不断发展与进步,人们对于信息科学化的认识,已由低层次向高层次发展,由原来的感性认识向理性认识提高,管理工作的重要性已逐渐被人们所认识,科学化的管理,使信息存储达到准确、快速、完善,并能提高工作管理效率,促进其发展。论文主要是对大学生实习成绩......
  • 计算机毕业设计项目推荐,个人知识管理系统 79004(开题答辩+程序定制+全套文案 )上万套实
    摘 要尽管我们每天面临的信息越来越多,信息过载与信息噪音越来越严重,但只要我们能充分利用个人知识管理技能,借助有效的个人知识管理软件相信战胜海量信息不再是困难。本课题在分析了个人知识管理现状以及对现有的个人知识管理网站进行研究比较的基础上,针对网络交流互助的特......
  • 150份Java计算机毕业设计项目推荐(源码+论文+PPT)
    2024最新Java项目选题推荐哈喽,大家好,大四的同学马上要开始做毕业设计了,大家做好准备了吗?博主给大家详细整理了150个Java项目选题推荐,有什么疑问可在评论区留言哦!需要链接请私信我哦!或者在评论区打出来!02:基于Java+springboot的报名系统03:基于Java+springboot的培训管理系统......
  • java项目cpu占用高排查方法(chatgpt)
    Java项目中CPU占用持续过高的问题可能与多种因素有关,比如死循环、线程问题、垃圾回收频繁等。以下是排查Java项目CPU占用过高的常见步骤和方法:1.初步诊断系统层面问题使用系统工具初步检查CPU占用高的进程和线程情况:使用top命令:找出哪个Java进程(PID)占用了较高......
  • vue3入门教程,一站学会全套vue!
    vue3vue3作为前端重要的框架,学会vue可以让你更加了解前端。本博客致力于让你一站学会vue3的全部内容,从小白到高手。全是干货,准备好了吗?文章目录vue3创建工程文档结构核心语法模板语法插值语法指令语法无参指令有参指令自定义指令setupsetup函数setup语法糖响应式数......
  • SQL:Windows下MySQL的安装教程(超详细)
    一.系统环境:操作系统:Windows11;MySQL版本:mysql-community-8.0.40.0;二.MySQL下载:访问MySQL官网下载地址:https://www.mysql.com/,点击DOWNLOADS;跳转后页面下滑找到框选链接并点击;跳转后点击框选链接;跳转后点击Download;5.当前下载页面需要登录但是也可以点......
  • JavaWeb合集14-全局异常处理器
    十四、全局异常处理器全局异常处理器(GlobalExceptionHandler)是指一种机制,用于集中处理应用程序中未被捕获的异常。全局异常处理器可以用来统一处理整个应用程序中可能出现的异常,从而确保在出现未预期的错误时,程序能够以一种优雅的方式处理这些错误,并提供一致的错误响应......
  • JavaWeb合集17-拦截器(Interceptor)和过滤器(Filter)
    十七、拦截器和过滤器在JavaWeb开发中,拦截器(Interceptor)和过滤器(Filter)都是用于在请求处理前后执行某些操作的机制。虽然它们的功能相似,但在实现方式、使用场景和灵活性方面有一些重要的区别。1、拦截器和过滤器的区别及选择1.1拦截器定义:拦截器是Spring框架提供......