JavaLite 指南:構建 RESTful CRUD 應用

REST
Remote
0
11:51 AM · Dec 01 ,2025

 

1. 簡介JavaLite 是一個用於簡化開發者在構建應用程序時需要處理的常見任務的框架集合。

在本教程中,我們將重點關注 JavaLite 的功能,重點構建一個簡單的 API。

2. Setup

在整個教程中,我們將創建一個簡單的 RESTful CRUD 應用程序。為此,我們將使用 ActiveWeb 和 ActiveJDBC – 兩個 ActiveWeb 與 JavaLite 集成的框架。

所以,讓我們開始,添加第一個所需的依賴項:

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>activeweb</artifactId>
    <version>1.15</version>
</dependency>

ActiveWeb 構件包含 ActiveJDBC,因此無需單獨添加。請注意,最新 activeweb 版本可以在 Maven Central 上找到。

我們需要第二個依賴項是 數據庫連接器。對於這個示例,我們將使用 MySQL,因此我們需要添加:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.45</version>
</dependency>

再次,最新 mysql-connector-java 依賴項可以在 Maven Central 上找到。

我們必須添加的最後一個依賴項是特定於 JavaLite 的內容:

<plugin>
    <groupId>org.javalite</groupId>
    <artifactId>activejdbc-instrumentation</artifactId>
    <version>1.4.13</version>
    <executions>
        <execution>
            <phase>process-classes</phase>
            <goals>
                <goal>instrument</goal>
            </goals>
        </execution>
    </executions>
</plugin>

最新 activejdbc-instrumentation 插件也可以在 Maven Central 上找到。

所有這些都已到位,在開始實體、表和映射之前,我們將確保 一個受支持的數據庫 正在運行。正如我們之前所説,我們將使用 MySQL。

現在,我們準備好開始面向關係到對象映射。

3. Object-Relational Mapping

3.1. Mapping and Instrumentation

Let’s get started by creating a Product class that will be our main entity:

public class Product {}

And, let’s also create the corresponding table for it:

CREATE TABLE PRODUCTS (
    id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
    name VARCHAR(128)
);

Finally, we can modify our Product class to do the mapping:

public class Product extends Model {}

We only need to extend org.javalite.activejdbc.Model class. ActiveJDBC infers DB schema parameters from the database. Thanks to this capability, there’s no need to add getters and setters or any annotation.

Furthermore, ActiveJDBC automatically recognizes that Product class needs to be mapped to PRODUCTS table. It makes use of English inflections to convert singular form of a model to a plural form of a table. And yes, it works with exceptions as well.

There’s one final thing that we will need to make our mapping work: instrumentation. Instrumentation is an extra step required by ActiveJDBC that will allow us to play with our Product class as if it had getters, setters, and DAO-like methods.

After running instrumentation, we’ll be able to do things like:

Product p = new Product();
p.set("name","Bread");
p.saveIt();

or:

List<Product> products = Product.findAll();

This is where activejdbc-instrumentation plugin comes in. As we already have the dependency in our pom, we should see classes being instrumented during build:

...
[INFO] --- activejdbc-instrumentation:1.4.11:instrument (default) @ javalite ---
**************************** START INSTRUMENTATION ****************************
Directory: ...\tutorials\java-lite\target\classes
Instrumented class: .../tutorials/java-lite/target/classes/app/models/Product.class
**************************** END INSTRUMENTATION ****************************
...

Next, we’ll create a simple test to make sure this is working.

3.2. Testing

Finally, to test our mapping, we’ll follow three simple steps: open a connection to the database, save a new product and retrieve it:

@Test
public void givenSavedProduct_WhenFindFirst_ThenSavedProductIsReturned() {
    
    Base.open(
      "com.mysql.jdbc.Driver",
      "jdbc:mysql://localhost/dbname",
      "user",
      "password");

    Product toSaveProduct = new Product();
    toSaveProduct.set("name", "Bread");
    toSaveProduct.saveIt();

    Product savedProduct = Product.findFirst("name = ?", "Bread");

    assertEquals(
      toSaveProduct.get("name"), 
      savedProduct.get("name"));
}

Note that all this (and more) is possible by only having an empty model and instrumentation.

4. Controllers

現在我們的映射已準備就緒,我們可以開始思考我們的應用程序及其 CRUD 方法。

為此,我們將使用控制器來處理 HTTP 請求。

讓我們創建一個 ProductsController

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // ...
    }

}

通過這種實現,ActiveWeb 將自動將 index() 方法映射到以下 URI:

http://<host>:<port>/products

帶有 @RESTful 註解的控制器提供了一組自動映射到不同 URI 的固定方法。 讓我們看看哪些對我們的 CRUD 示例有用:

Controller 方法 HTTP 方法 URI
CREATE create() POST http://host:port/products
READ ONE show() GET http://host:port/products/{id}
READ ALL index() GET http://host:port/products
UPDATE update() PUT http://host:port/products/{id}
DELETE destroy() DELETE http://host:port/products/{id}

如果我們將這些方法添加到我們的 ProductsController 中:

@RESTful
public class ProductsController extends AppController {

    public void index() {
        // code to get all products
    }

    public void create() {
        // code to create a new product
    }

    public void update() {
        // code to update an existing product
    }

    public void show() {
        // code to find one product
    }

    public void destroy() {
        // code to remove an existing product 
    }
}

在繼續實現邏輯之前,我們將快速查看我們需要配置的一些內容。

5. 配置

ActiveWeb 主要基於約定,項目結構是其中的一個例子。 ActiveWeb 項目需要遵循預定義的包佈局:

src
 |----main
       |----java.app
       |     |----config
       |     |----controllers
       |     |----models
       |----resources
       |----webapp
             |----WEB-INF
             |----views

有一特定的包需要我們重點關注,即 pp.config

在該包中,我們將創建三個類:

public class DbConfig extends AbstractDBConfig {
    @Override
    public void init(AppContext appContext) {
        this.configFile("/database.properties");
    }
}

該類配置數據庫連接, 使用項目根目錄下包含所需參數的屬性文件:

development.driver=com.mysql.jdbc.Driver
development.username=user
development.password=password
development.url=jdbc:mysql://localhost/dbname

這將自動創建連接,替換我們在第一個映射測試的第一行所做的事情。

第二個需要包含在 app.config 包中的類是:

public class AppControllerConfig extends AbstractControllerConfig {
 
    @Override
    public void init(AppContext appContext) {
        add(new DBConnectionFilter()).to(ProductsController.class);
    }
}

這段代碼 將連接與我們的控制器綁定

第三個類 將配置我們的應用程序上下文:

public class AppBootstrap extends Bootstrap {
    public void init(AppContext context) {}
}

在創建三個類之後,關於配置的最後一件事情是 創建我們的 web.xml 文件webapp/WEB-INF 目錄下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns=...>

    <filter>
        <filter-name>dispatcher</filter-name>
        <filter-class>org.javalite.activeweb.RequestDispatcher</filter-class>
        <init-param>
            <param-name>exclusions</param-name>
            <param-value>css,images,js,ico</param-value>
        </init-param>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>dispatcher</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

現在配置已完成,我們可以繼續添加我們的邏輯。

6. 實現 CRUD 邏輯

藉助我們的 產品 類提供的 DAO 樣式的能力,添加基本 CRUD 功能非常簡單:

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();    

    public void index() {
        List<Product> products = Product.findAll();
        // ...
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        p.fromMap(payload);
        p.saveIt();
        // ...
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        // ...
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        p.delete();
        // ...
    }
}

簡單吧?但是,這還沒有返回任何內容。為了實現這一點,我們需要創建一些視圖。

7. Views

ActiveWeb uses FreeMarker as a templating engine, and all its templates should be located under src/main/webapp/WEB-INF/views.

Inside that directory, we will place our views in a folder called products.

Let’s create our first template called _product.ftl.

{
    "id" : ${product.id},
    "name" : "${product.name}"
}

It’s pretty clear at this point that this is a JSON response. Of course, this will only work for one product, so let’s go ahead and create another template called index.ftl.

[<@render partial="product" collection=products/>]

This will basically render a collection named products, with each one formatted by _product.ftl.

Finally, we need to bind the result from our controller to the corresponding view.

@RESTful
public class ProductsController extends AppController {

    public void index() {
        List<Product> products = Product.findAll();
        view("products", products);
        render();
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        view("product", p);
        render("_product");
    }
}

In the first case, we’re assigning products list to our template collection named also products.

Then, as we’re not specifying any view, index.ftl will be used.

In the second method, we’re assigning product p to element product in the view and we’re explicitly saying which view to render.

We could also create a view message.ftl.

{
    "message" : "${message}",
    "code" : ${code}
}

And then call it form any of our ProductsController's method:

view("message", "There was an error.", "code", 200);
render("message");

Let’s now see our final ProductsController:

@RESTful
public class ProductsController extends AppController {

    private ObjectMapper mapper = new ObjectMapper();

    public void index() {
        view("products", Product.findAll());
        render().contentType("application/json");
    }

    public void create() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        Product p = new Product();
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully saved product id " + p.get("id"), "code", 200);
        render("message");
    }

    public void update() {
        Map payload = mapper.readValue(getRequestString(), Map.class);
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.fromMap(payload);
        p.saveIt();
        view("message", "Successfully updated product id " + id, "code", 200);
        render("message");
    }

    public void show() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        view("product", p);
        render("_product");
    }

    public void destroy() {
        String id = getId();
        Product p = Product.findById(id);
        if (p == null) {
            view("message", "Product id " + id + " not found.", "code", 200);
            render("message");
            return;
        }
        p.delete();
        view("message", "Successfully deleted product id " + id, "code", 200);
        render("message");
    }

    @Override
    protected String getContentType() {
        return "application/json";
    }

    @Override
    protected String getLayout() {
        return null;
    }
}

At this point, our application is done and we’re ready to run it.

8. 運行應用程序

我們將使用 Jetty 插件:

<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.8.v20171121</version>
</plugin>

查找 Maven Central 中最新版本的 jetty-maven-plugin

現在,我們可以運行我們的應用程序:

mvn jetty:run

讓我們創建一些產品:

$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Water"}'
{
    "message" : "成功保存產品 id 1",
    "code" : 200
}
$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Bread"}'
{
    "message" : "成功保存產品 id 2",
    "code" : 200
}

.. 讀取它們:

$ curl -X GET http://localhost:8080/products
[
    {
        "id" : 1,
        "name" : "Water"
    },
    {
        "id" : 2,
        "name" : "Bread"
    }
]

.. 更新其中一個:

$ curl -X PUT http://localhost:8080/products/1 
  -H 'content-type: application/json' 
  -d '{"name":"Juice"}'
{
    "message" : "成功更新產品 id 1",
    "code" : 200
}

… 讀取我們剛剛更新的那個:

$ curl -X GET http://localhost:8080/products/1
{
    "id" : 1,
    "name" : "Juice"
}

最後,我們可以刪除一個:

$ curl -X DELETE http://localhost:8080/products/2
{
    "message" : "成功刪除產品 id 2",
    "code" : 200
}

9. 結論

JavaLite 擁有許多工具,可以幫助開發者 幾分鐘內啓動應用程序。然而,儘管基於約定結果出更簡潔、更簡單的代碼,但理解類、包和文件的命名和位置需要一些時間。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.