JavaLite指南-构建RESTful CRUD应用程序

2025/04/08

1. 简介

JavaLite是一个框架集合,用于简化每个开发人员在构建应用程序时必须处理的常见任务

在本教程中,我们将了解专注于构建简单API的JavaLite功能。

2. 设置

在本教程中,我们将创建一个简单的RESTful CRUD应用程序。为此,我们将使用ActiveWeb和ActiveJDBC-JavaLite集成的两个框架。

那么,让我们开始并添加我们需要的第一个依赖:

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

ActiveWeb工件包含ActiveJDBC,因此无需单独添加。请注意,可以在Maven Central中找到最新的activeweb版本

我们需要的第二个依赖是数据库连接器,在这个例子中,我们将使用MySQL,因此我们需要添加:

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

同样,可以在Maven Central上找到最新的mysql-connector-java依赖。

我们必须添加的最后一个依赖是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. 对象关系映射

3.1 映射和插桩

让我们首先创建一个作为我们主要实体的Product类

public class Product {}

并且,我们还为其创建相应的表

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

最后,我们可以修改Product类来进行映射

public class Product extends Model {}

我们只需要扩展org.javalite.activejdbc.Model类,ActiveJDBC从数据库推断出DB模式参数,借助此功能,无需添加Getter和Setter或任何注解

此外,ActiveJDBC会自动识别Product类需要映射到PRODUCTS表,它利用英语词形变化将模型的单数形式转换为表的复数形式。当然,它也能处理异常。

为了让映射工作,我们还需要做最后一件事:检测。检测是ActiveJDBC所需的一个额外步骤,它允许我们使用Product类,就好像它有Getter、Setter和类似DAO的方法一样。

运行检测后,我们将能够做如下事情:

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

或者:

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

这就是activejdbc-instrumentation插件的作用所在,由于我们的pom中已经有了依赖,我们应该看到在构建过程中被检测的类:

...
[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 ****************************
...

接下来,我们将创建一个简单的测试来确保它正常运行。

3.2 测试

最后,为了测试我们的映射,我们将遵循三个简单的步骤:打开与数据库的连接,保存新Product并检索它:

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

请注意,仅凭一个空的模型和插桩,就可以实现所有这些(以及更多)。

4. 控制器

现在我们的映射已经准备好了,可以开始考虑我们的应用程序及其CRUD方法。

为此,我们将使用处理HTTP请求的控制器。

让我们创建ProductsController:

@RESTful
public class ProductsController extends AppController {

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

通过此实现,ActiveWeb将自动将index()方法映射到以下URI:

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

用@RESTful标注的控制器提供了一组固定的方法,这些方法会自动映射到不同的URI,让我们看看哪些方法对我们的CRUD示例有用:

控制器方法 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) {}
}

创建这三个类之后,关于配置的最后一件事是在webapp/WEB-INF目录下创建web.xml文件:

<?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>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>

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

现在配置已经完成,我们可以继续添加我们的逻辑。

6. 实现CRUD逻辑

借助我们的Product类提供的类似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. 视图

ActiveWeb使用FreeMarker作为模板引擎,其所有模板都应位于src/main/webapp/WEB-INF/views下

在该目录中,我们将把视图放在名为products的文件夹中(与我们的控制器相同),让我们创建第一个名为_product.ftl的模板:

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

到目前为止,很明显这是一个JSON响应。当然,这只适用于单个Product,所以让我们继续创建另一个名为index.ftl的模板:

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

这将基本上呈现一个名为projects的集合,每个Product都由_product.ftl格式化

最后,我们需要将控制器的结果绑定到相应的视图

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

在第一种情况下,我们将products列表分配给名为products的模板集合。

然后,由于我们没有指定任何视图,因此将使用index.ftl。

在第二种方法中,我们将产品p分配给视图中的product产品,并明确说明要渲染哪个视图。

我们还可以创建一个视图message.ftl:

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

然后从我们的任何ProductsController的方法中调用它:

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

现在让我们看看最终的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;
    }
}

至此,我们的应用程序已完成并准备运行它。

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

让我们创建几个Product:

$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Water"}'
{
    "message" : "Successfully saved product id 1",
    "code" : 200
}
$ curl -X POST http://localhost:8080/products 
  -H 'content-type: application/json' 
  -d '{"name":"Bread"}'
{
    "message" : "Successfully saved product 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" : "Successfully updated product 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" : "Successfully deleted product id 2",
    "code" : 200
}

9. 总结

JavaLite有很多工具可以帮助开发人员在几分钟内启动并运行应用程序。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章