Advanced Java là gì?

Xem thử

Một người học cài JDK cũ từ nhiều năm trước, mở dự án đầu tiên của khóa và thấy bước build báo lỗi. Hai giờ sau họ vẫn đang tìm lỗi trong mã, trong khi nguyên nhân chỉ là phiên bản công cụ trên máy không đáp ứng yêu cầu của bài. Họ nghĩ mình học kém; thật ra họ chưa từng kiểm tra môi trường.

Bài mở đầu này làm hai việc. Thứ nhất, nó nói rõ khóa học dạy gì và theo thứ tự nào, để mỗi chương sau nằm đúng chỗ trong một bức tranh. Thứ hai, nó cho bạn một chương trình nhỏ để kiểm tra máy mình trước khi bắt đầu, vì một môi trường không đúng làm mất nhiều thời gian hơn bất kỳ lỗi nào trong mã.

Sau bài này, bạn có thể

  • Mô tả Advanced Java gồm những gì và phân biệt nó với Core Java.
  • Vẽ đường đi của một yêu cầu qua các tầng của ứng dụng backend và đặt mỗi chương của khóa vào đúng tầng.
  • Chạy chương trình kiểm tra môi trường và đọc kết quả PASS, WARN, FAIL.
  • Biết cách dùng kho mã của khóa: mỗi lab nằm trong một thư mục, có README, chạy và kiểm thử độc lập.

Advanced Java là gì

Core Java là ngôn ngữ và thư viện chuẩn: cú pháp, hướng đối tượng, collections, xử lý ngoại lệ, đa luồng, vào ra. Advanced Java là mọi thứ bạn cần thêm để viết ứng dụng doanh nghiệp, web và backend: truy cập cơ sở dữ liệu (JDBC, Hibernate), tầng web (Servlet, JSP), khung ứng dụng (Spring, Spring Boot), bảo mật, kiến trúc nhiều dịch vụ và kiểm thử. Nó không phải một ngôn ngữ khác: đó là các lớp được xây trên Java mà bạn nếu không dùng thì phải tự viết (bài sau đo chính điều đó).

Điều kiện tiên quyết: bạn cần nắm Core Java (cú pháp, OOP, collections, ngoại lệ, đa luồng cơ bản) trước khi học khóa này.

Một yêu cầu đi qua các tầng

Cách đọc hình: yêu cầu đi từ trình duyệt qua bộ lọc bảo mật, tầng web, tầng service, tầng dữ liệu rồi tới cơ sở dữ liệu. Dòng chữ nhỏ trong mỗi ô cho biết chương nào của khóa học nói về tầng đó. Ghi chú dưới hình: chương 13 và 14 nói về điều thay đổi khi chia ngăn xếp này thành nhiều dịch vụ, chương 15 về kiểm thử.

Đang tải Animation…

Lộ trình khóa học

Khóa học đi từ dưới lên: từ cách Java nói chuyện với cơ sở dữ liệu, qua tầng web, ORM, tới hệ sinh thái Spring, rồi tới kiến trúc nhiều dịch vụ và kiểm thử. Mỗi chương có bài học kèm các lab chạy được trong kho mã của khóa; chương cuối là một dự án tổng hợp.

ChươngNội dungBạn sẽ làm và đo gì
1. Giới thiệuBức tranh tổng thể, môi trường, socket so với servletKiểm tra máy; so máy chủ viết tay với servlet container
2. JDBCKết nối, truy vấn, giao dịch, poolKết nối tới MySQL, PreparedStatement, giao dịch, batch
3. ServletVòng đời, request, response, session, filterTomcat nhúng, ánh xạ URL, filter
4. JSPTrang web động, MVC với servletTrang JSP, EL, tag
5. HibernateORM, ánh xạ, HQL, hiệu năngCấu hình, N+1, khóa, bộ nhớ đệm
6. Spring CoreIoC, DI, phạm vi beanContainer, cấu hình, vòng đời bean
7. Spring MVCDispatcherServlet, controller, validationLuồng xử lý yêu cầu, lỗi
8. Spring BootTự cấu hình, REST, ActuatorKhởi động, cấu hình, quan sát
9. Spring Data JPARepository, truy vấn, giao dịchPhân trang, N+1, giao dịch
10. Spring JDBCJdbcTemplateBatch, xử lý ngoại lệ
11. Spring SecurityXác thực, phân quyền, JWTChuỗi bộ lọc, mã hóa mật khẩu, token
12. Spring AOPProxy, adviceProxy, thứ tự advice, giao dịch
13. Spring CloudDiscovery, Feign, Gateway, Config, TracingĐo cửa sổ lỗi, circuit breaker, vết
14. MicroservicesKiến trúc, nhất quán dữ liệu, DockerCái giá của việc tách, saga, Docker Compose
15. JUnit & TestingJUnit, Mockito, kiểm thử SpringVòng đời, TDD, lát cắt, Testcontainers

Mọi bài sau đều theo một khung: mở bằng một sự cố có thật, hình có phần "Cách đọc hình", số đo từ chính lab, bảng tra cứu khi gặp sự cố, bài thực hành có tiêu chí nghiệm thu và câu hỏi tự kiểm tra.

Chuẩn bị môi trường và dự án

Mọi thí nghiệm của bài chạy trong một dự án Maven nhỏ. Mục này nói rõ cần cài gì, khai báo thư viện nào và cấu hình ra sao; mã của từng lớp được trình bày ngay tại chỗ nó được dùng, kèm giải thích, nên bạn không cần mở nơi nào khác. Số liệu và thông báo lỗi trong bài được đo từ chính mã đó (JDK 17, Maven 3.9). Bản sao lưu cả dự án nằm ở kho mã của khóa học, thư mục 01-nhap-mon/01-kiem-tra-moi-truong, chỉ để bạn clone cho nhanh nếu không muốn tự gõ lại.

Yêu cầu môi trường

Thành phầnPhiên bản đã kiểm chứngGhi chú
JDK17 trở lênjava -version
Maven3.9 trở lênmvn -v
Cổng 3307tùy chọnMySQL của các bài JDBC; WARN nếu chưa chạy
Dockerbất kỳ bản gần đâykhông bắt buộc cho bài này; các bài JDBC, kiểm thử và microservices cần tới

Cấu trúc

text
01-kiem-tra-moi-truong/
  pom.xml                       Maven thuần, release 17, JUnit và AssertJ cho test
  src/main/java/dev/canhdominich/intro/env/
    EnvCheck.java               sáu phép kiểm tra và mã thoát

Khai báo thư viện: pom.xml

pom.xml

Dự án Maven thuần, chỉ cần JUnit 6.0.3 và AssertJ 3.27.7 cho phần test; chương trình chính EnvCheck không dùng thư viện nào ngoài JDK. maven.compiler.release đặt là 17, mức tối thiểu của khóa học. Plugin exec-maven-plugin chạy EnvCheck bằng mvn -q compile exec:exec và maven-surefire-plugin 3.5.6 chạy test.

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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>dev.canhdominich.javabackend</groupId>
    <artifactId>intro-kiem-tra-moi-truong</artifactId>
    <version>1.0.0</version>
    <name>Nhap mon - Kiem tra moi truong</name>

    <properties>
        <maven.compiler.release>17</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <main.class>dev.canhdominich.intro.env.EnvCheck</main.class>
        <main.arg>none</main.arg>
        <junit.version>6.0.3</junit.version>
        <assertj.version>3.27.7</assertj.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>${assertj.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.5.6</version>
            </plugin>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>3.6.3</version>
                <configuration>
                    <executable>java</executable>
                    <arguments>
                        <argument>-classpath</argument>
                        <classpath/>
                        <argument>${main.class}</argument>
                        <argument>${main.arg}</argument>
                    </arguments>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Các thành phần của bài

Bài có một lab nhỏ, EnvCheck, để kiểm tra máy bạn. Nó không dùng thư viện nào ngoài JDK. Check ghi một kết quả và mức độ của nó; run() thực hiện sáu phép kiểm tra: phiên bản JDK (phải từ 17), bảng mã mặc định (UTF-8 tránh nhiều lỗi hiển thị tiếng Việt), bộ nhớ heap tối đa, Maven (chạy lệnh mvn -v), Docker (docker version) và cổng 3307 nơi MySQL của các bài JDBC sẽ chạy. FAIL nghĩa là bạn chưa thể làm bài; WARN nghĩa là một phần khóa học (Docker, MySQL) chưa dùng được nhưng vẫn học tiếp được các bài khác. Chương trình thoát với mã 1 nếu có FAIL, nên dùng được trong một script:

src/main/java/dev/canhdominich/intro/env/EnvCheck.java

java
package dev.canhdominich.intro.env;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class EnvCheck {

    public enum Level {
        PASS, WARN, FAIL
    }

    public record Check(Level level, String text) {

        @Override
        public String toString() {
            return String.format("%-5s %s", level, text);
        }
    }

    public static void main(String[] args) {
        List<Check> checks = run();
        checks.forEach(System.out::println);
        System.exit(checks.stream().anyMatch(c -> c.level() == Level.FAIL) ? 1 : 0);
    }

    public static List<Check> run() {
        List<Check> checks = new ArrayList<>();
        int feature = Runtime.version().feature();
        checks.add(new Check(feature >= 17 ? Level.PASS : Level.FAIL, "JDK " + Runtime.version() + " (" + System.getProperty("java.vendor") + "), the course needs 17 or newer"));
        checks.add(new Check(Level.PASS, "default charset " + Charset.defaultCharset() + ", native encoding " + System.getProperty("native.encoding")));
        checks.add(new Check(Runtime.getRuntime().maxMemory() >= 256L * 1024 * 1024 ? Level.PASS : Level.WARN,
                "maximum heap " + Runtime.getRuntime().maxMemory() / (1024 * 1024) + " MB, processors " + Runtime.getRuntime().availableProcessors()));
        String maven = firstLine("mvn", "-v");
        checks.add(new Check(maven != null ? Level.PASS : Level.FAIL, maven != null ? maven : "Maven: the command mvn was not found on the PATH"));
        String docker = firstLine("docker", "version", "--format", "{{.Server.Version}}");
        checks.add(new Check(docker != null && !docker.isBlank() ? Level.PASS : Level.WARN,
                docker != null && !docker.isBlank() ? "Docker engine " + docker : "Docker is not available: the MySQL and Testcontainers labs need it"));
        boolean mysql = reachable("localhost", 3307);
        checks.add(new Check(mysql ? Level.PASS : Level.WARN, mysql ? "something listens on localhost:3307 (the MySQL of the JDBC labs)" : "nothing listens on localhost:3307: start it with the docker-compose.yml of the JDBC labs"));
        return checks;
    }

    static String firstLine(String... command) {
        try {
            Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
            if (!process.waitFor(15, TimeUnit.SECONDS)) {
                process.destroyForcibly();
                return null;
            }
            if (process.exitValue() != 0) {
                return null;
            }
            return new String(process.getInputStream().readAllBytes()).lines().findFirst().orElse("");
        } catch (IOException | InterruptedException e) {
            return null;
        }
    }

    static boolean reachable(String host, int port) {
        try (Socket socket = new Socket()) {
            socket.connect(new InetSocketAddress(host, port), 500);
            return true;
        } catch (IOException e) {
            return false;
        }
    }
}

EnvCheckTest kiểm tra rằng máy bạn không có FAIL, và hai hàm phụ biết nói "không" đúng cách: một lệnh không tồn tại cho null, một cổng đóng cho false:

src/test/java/dev/canhdominich/intro/env/EnvCheckTest.java

java
package dev.canhdominich.intro.env;

import static org.assertj.core.api.Assertions.assertThat;

import dev.canhdominich.intro.env.EnvCheck.Check;
import dev.canhdominich.intro.env.EnvCheck.Level;
import java.util.List;
import org.junit.jupiter.api.Test;

class EnvCheckTest {

    @Test
    void thisMachineMeetsTheMinimumForTheCourse() {
        List<Check> checks = EnvCheck.run();

        assertThat(checks).noneMatch(check -> check.level() == Level.FAIL);
        assertThat(checks).hasSize(6);
    }

    @Test
    void aMissingCommandIsReportedAsNull() {
        assertThat(EnvCheck.firstLine("this-command-does-not-exist")).isNull();
    }

    @Test
    void aClosedPortIsNotReachable() {
        assertThat(EnvCheck.reachable("localhost", 1)).isFalse();
    }
}

Chạy bằng mvn -q compile exec:exec. Kết quả trên máy đã kiểm chứng của khóa (phiên bản của bạn có thể khác, miễn là không có FAIL):

text
PASS  JDK 17.0.15+6-Ubuntu-0ubuntu120.04 (Ubuntu), the course needs 17 or newer
PASS  default charset UTF-8, native encoding UTF-8
PASS  maximum heap 3972 MB, processors 4
PASS  Apache Maven 3.9.12 (848fbb4bf2d427b72bdb2471c22fced7ebd9a7a1)
PASS  Docker engine 23.0.2
PASS  something listens on localhost:3307 (the MySQL of the JDBC labs)

Cách đọc từng dòng:

DòngNghĩaNếu không đạt
JDKPhiên bản Java đang chạy phải từ 17FAIL: cài JDK 17 trở lên, đặt JAVA_HOME
default charsetBảng mã mặc định của JVMNếu không phải UTF-8, thêm -Dfile.encoding=UTF-8 khi gặp lỗi chữ
maximum heapBộ nhớ tối đa JVM được dùngWARN nếu dưới 256 MB: một số lab cần nhiều hơn
MavenCông cụ build, phải có trong PATHFAIL: cài Maven 3.9 trở lên
DockerCần cho MySQL, Testcontainers và chương microservicesWARN: chỉ cần từ chương 2
localhost:3307MySQL của các bài JDBCWARN: khởi động bằng docker-compose.yml của các bài JDBC

Cách học với khóa này

  • Chạy trước khi đọc kết quả. Mỗi bài cho mã, cách chạy và kết quả mong đợi. Hãy chạy rồi so với kết quả, đừng chỉ đọc.
  • Mỗi lab là một dự án Maven độc lập trong kho mã của khóa (README của kho). Vào thư mục lab, đọc README.md (yêu cầu, cấu trúc, lệnh chạy, kết quả mong đợi, lỗi thường gặp), rồi chạy.
  • Làm bài thực hành. Mỗi bài có các bước đánh số với tiêu chí nghiệm thu; bước làm hỏng cố ý quan trọng nhất vì bạn thấy lỗi thật trông ra sao.
  • Ghi lại điều bạn thấy. Con số trên máy bạn khác con số trong bài là bình thường; thứ tự và quan hệ giữa chúng mới là điều cố định.

Quyết định thiết kế và đánh đổi

Quyết địnhLựa chọnĐánh đổi
Học bằng lab chạy đượcMỗi bài một dự án nhỏMất thời gian cài đặt lúc đầu, đổi lại hiểu thật
Xây từ dưới lên (JDBC trước Spring)Hiểu cơ chế dưới frameworkLâu hơn học thẳng framework nhưng ít "phép màu"
Kiểm tra môi trường trướcMột lệnh, sáu kiểm traThêm một bước nhưng tránh lỗi khó hiểu về sau
Docker cho cơ sở dữ liệu và kiểm thửMôi trường lặp lại đượcCần cài Docker

Playbook: gặp sự cố ở đầu khóa thì làm gì

Triệu chứngKiểm tra đầu tiênNguyên nhân hay gặpCách xử lý
Bước build của lab báo lỗi biên dịch phiên bảnEnvCheck dòng JDKJDK cũ hơn 17Cài JDK 17 trở lên
mvn không chạyEnvCheck dòng MavenMaven chưa có trong PATHCài và mở lại terminal
Chữ tiếng Việt hiện saiDòng default charsetBảng mã mặc định không phải UTF-8Đặt -Dfile.encoding=UTF-8
Lab MySQL không kết nốiDòng localhost:3307MySQL chưa chạyKhởi động bằng Docker Compose của bài JDBC
Lab Docker báo lỗiDòng DockerDocker chưa cài hoặc chưa chạyCài Docker và thử docker version
Con số khác bàiMáy và tảiCấu hình máy khácSo thứ tự và quan hệ thay vì giá trị tuyệt đối

Lỗi thường gặp khi chạy lab

Triệu chứngNguyên nhânCách xử lý
FAIL JDK ... the course needs 17 or newerĐang dùng JDK cũ hơn 17Cài JDK 17 trở lên và đặt JAVA_HOME; chạy lại java -version
FAIL Maven: the command mvn was not foundMaven chưa có trong PATHCài Maven 3.9 và mở lại terminal; kiểm tra mvn -v
WARN Docker is not availableDocker chưa cài hoặc chưa chạyCài Docker và chạy docker version; chỉ cần từ chương JDBC trở đi

Bài thực hành

Làm trong dự án của bài (thư mục 01-nhap-mon/01-kiem-tra-moi-truong). Sau mỗi bước, so kết quả với tiêu chí nghiệm thu.

  1. Chạy kiểm tra. mvn -q compile exec:exec. Tiêu chí: không có dòng FAIL; ghi lại dòng WARN (nếu có) và quyết định khi nào xử lý.
  2. Chạy bộ test. mvn test. Tiêu chí: Tests run: 3, Failures: 0, Errors: 0.
  3. Giả lập môi trường sai. Đổi điều kiện feature >= 17 thành feature >= 99 trong EnvCheck.run. Tiêu chí: dòng JDK thành FAIL, chương trình thoát với mã 1 (echo $? ngay sau khi chạy), và EnvCheckTest đỏ; đổi lại sau khi xong.
  4. Thêm một kiểm tra. Thêm một kiểm tra thứ bảy cho một công cụ bạn cần (ví dụ git --version) bằng firstLine. Tiêu chí: thêm một dòng PASS hoặc WARN; cập nhật hasSize trong test.
  5. Đọc kho mã. Mở README của kho mã và của một lab JDBC bất kỳ. Tiêu chí: tìm được mục yêu cầu môi trường, lệnh chạy và kết quả mong đợi của lab đó.

Hiểu lầm và giới hạn

  • "Advanced Java là một ngôn ngữ khác." Nó là các lớp thư viện và khung xây trên cùng ngôn ngữ Java.
  • "Học thẳng Spring Boot là đủ." Spring che nhiều cơ chế; khóa này đi từ dưới lên để bạn biết thứ nó che.
  • "Môi trường đúng là chuyện nhỏ." Môi trường sai làm mất thời gian và dễ tưởng nhầm là lỗi của mình.
  • Kết quả EnvCheck ở trên thuộc về máy đã kiểm chứng (JDK 17.0.15, Maven 3.9.12, Docker 23.0.2, 4 lõi); bài chưa kiểm tra phiên bản Git, IDE, mạng, quyền ghi đĩa hay dung lượng đĩa trống (một số lab cần vài trăm MB).

Tự kiểm tra

  1. Advanced Java khác Core Java ở điểm nào, và có phải là một ngôn ngữ khác không?
  2. Kể tên các tầng của một ứng dụng backend theo thứ tự yêu cầu đi qua.
  3. FAIL và WARN trong EnvCheck khác nhau thế nào?
  4. Vì sao khóa học đi từ JDBC lên Spring chứ không bắt đầu bằng Spring Boot?
  5. Khi một lab cho con số khác trong bài, bạn so sánh điều gì?

Gợi ý đáp án. (1) Advanced Java là các thư viện và khung cho ứng dụng doanh nghiệp xây trên cùng ngôn ngữ Java. (2) Bộ lọc bảo mật, tầng web, tầng service, tầng dữ liệu, cơ sở dữ liệu. (3) FAIL chặn bạn làm bài, WARN chỉ chặn một phần (như Docker hoặc MySQL). (4) Để hiểu cơ chế mà framework che đi. (5) Thứ tự và quan hệ giữa các con số, không phải giá trị tuyệt đối.

Điều cần nhớ

  • Advanced Java là các tầng xây trên Java: dữ liệu, web, khung ứng dụng, bảo mật, nhiều dịch vụ, kiểm thử.
  • Một yêu cầu đi qua bảo mật, web, service, dữ liệu; mỗi chương đào sâu một tầng.
  • Kiểm tra môi trường trước: JDK 17 trở lên, Maven, UTF-8; Docker và MySQL cho các chương sau.
  • Chạy lab, so kết quả, làm bước làm hỏng cố ý.

Đọc thêm

Bài tiếp theo đặt Core Java cạnh Advanced Java bằng một phép đo: cùng một ứng dụng web viết bằng socket thuần và bằng servlet.