A Guide to JavaLite – Building a RESTful CRUD application – JavaLite指南 – 构建一个RESTful CRUD应用程序

最后修改: 2018年 1月 20日

中文/混合/英文(键盘快捷键:t)

 

1. Introduction

1.介绍

JavaLite is a collection of frameworks for simplifying common tasks that every developer has to deal with when building applications.

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

In this tutorial, we’re going to take a look at JavaLite features focused on building a simple API.

在本教程中,我们要看一下JavaLite的功能,重点是建立一个简单的API。

2. Setup

2.设置

Throughout this tutorial, we’ll create a simple RESTful CRUD application. In order to do that, we’ll use ActiveWeb and ActiveJDBC – two of the frameworks that JavaLite integrates with.

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

So, let’s get started and add the first dependency that we need:

所以,让我们开始吧,添加我们需要的第一个依赖。

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

ActiveWeb artifact includes ActiveJDBC, so there’s no need to add it separately. Please note that the latest activeweb version can be found in Maven Central.

ActiveWeb构件包含ActiveJDBC,所以不需要单独添加它。请注意,最新的activeweb版本可以在Maven中心找到。

The second dependency we need is a database connector. For this example, we’re going to use MySQL so we need to add:

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

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

Again, latest mysql-connector-java dependency can be found over on Maven Central.

同样,最新的mysql-connector-java依赖项可以在Maven中心找到。

The last dependency that we have to add is something specific to JavaLite:

我们必须添加的最后一个依赖性是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>

The latest activejdbc-instrumentation plugin can also be found in Maven Central.

最新的activejdbc-instrumentation插件也可以在Maven中心找到。

Having all this in place and before starting with entities, tables, and mappings, we’ll make sure that one of the supported databases is up and running. As we said before, we’ll use MySQL.

在所有这些就绪后,在开始处理实体、表和映射之前,我们将确保一个支持的数据库已经启动并运行。正如我们之前所说,我们将使用MySQL。

Now we’re ready to start with object-relational mapping.

现在我们准备开始进行对象-关系映射。

3. Object-Relational Mapping

3.对象-关系映射

3.1. Mapping and Instrumentation

3.1.绘图和仪器设备

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

让我们从创建一个Product类开始,它将是我们的主要实体

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.

我们只需要扩展org.javalite.activejdbc.Model类。ActiveJDBC从数据库中推断DB模式参数。由于这种能力,不需要添加getters和setters或任何注释

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.

此外,ActiveJDBC自动识别出Product class需要被映射到PRODUCTS table。它利用英语转折词将模型的单数形式转换成表的复数形式。是的,它对例外情况也有效。

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.

Instrumentation是ActiveJDBC需要的一个额外步骤,它将允许我们使用我们的Product类,就像它有getters、setters和类似DAO的方法。

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:

这就是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 ****************************
...

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

接下来,我们将创建一个简单的测试,以确保这是在工作。

3.2. Testing

3.2.测试

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

4.控制器

Now that our mapping is ready, we can start thinking about our application and its CRUD methods.

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

For that, we’re going to make use of controllers which process HTTP requests.

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

Let’s create our ProductsController:

让我们创建我们的ProductsController

@RESTful
public class ProductsController extends AppController {

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

}

With this implementation, ActiveWeb will automatically map index() method to the following URI:

通过这种实现,ActiveWeb将自动把index() 方法映射到以下URI。

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

Controllers annotated with @RESTful, provide a fixed set of methods automatically mapped to different URIs. Let’s see the ones that will be useful for our CRUD example:

带有@RESTful注释的控制器,提供了一套固定的方法,自动映射到不同的URI。让我们看看那些对我们的CRUD例子有用的方法。

Controller method HTTP method 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}

And if we add this set of methods to our ProductsController:

如果我们把这组方法添加到我们的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 
    }
}

Before moving on to our logic implementation, we’ll take a quick look at few things that we need to configure.

在进入我们的逻辑实现之前,我们将快速浏览一下我们需要配置的一些东西。

5. Configuration

5.配置

ActiveWeb is based mostly on conventions, project structure is an example of that. ActiveWeb projects need to follow a predefined package layout:

ActiveWeb主要基于惯例,项目结构就是一个例子。ActiveWeb项目需要遵循预定义的软件包布局

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

There’s one specific package that we need to take a look at – app.config.

有一个特定的包,我们需要看一下 – 一个pp.config

Inside that package we’re going to create three classes:

在这个包里,我们将创建三个类。

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

This class configures database connections using a properties file in the project’s root directory containing the required parameters:

该类使用项目根目录中包含所需参数的属性文件来配置数据库连接

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

This will create the connection automatically replacing what we did in the first line of our mapping test.

这将自动创建连接,取代我们在映射测试第一行中的做法。

The second class that we need to include inside app.config package is:

我们需要在app.config包内包含的第二个类是。

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

This code will bind the connection that we just configured to our controller.

这段代码 将绑定我们刚刚配置的连接到我们的控制器。

The third class will configure our app’s context:

第三个类配置我们应用程序的上下文

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

After creating the three classes, the last thing regarding configuration is creating our web.xml file under webapp/WEB-INF directory:

在创建了三个类之后,关于配置的最后一件事是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>

Now that configuration is done, we can go ahead and add our logic.

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

6. Implementing CRUD Logic

6.实现CRUD逻辑

With the DAO-like capabilities provided by our Product class, it’s super simple to add basic CRUD functionality:

通过我们的产品类所提供的类似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();
        // ...
    }
}

Easy, right? However, this isn’t returning anything yet. In order to do that, we have to create some views.

很简单,对吗?然而,这还没有返回任何东西。为了做到这一点,我们必须创建一些视图。

7. Views

7.观点

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

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

Inside that directory, we will place our views in a folder called products (same as our controller). Let’s create our first template called _product.ftl:

在该目录中,我们将把我们的视图放在一个名为products的文件夹中(与我们的控制器相同)。让我们创建我们的第一个模板,叫做_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:

在这一点上很清楚,这是一个JSON响应。当然,这只对一个产品有效,所以让我们继续创建另一个模板,叫做index.ftl

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

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

这基本上会呈现一个名为products的集合,每个集合的格式为_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.

在第一种情况下,我们将productslist分配给我们的模板集合,也命名为products

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

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

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

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

We could also create a view message.ftl:

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

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

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

然后在我们的ProductsController的任何方法中调用它。

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

Let’s now see our final ProductsController:

现在让我们看看我们最终的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. Running the Application

8.运行应用程序

We’ll use Jetty plugin:

我们将使用Jetty插件。

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

Find latest jetty-maven-plugin in Maven Central.

在Maven中心查找最新的jetty-maven-plugin

And we’re ready, we can run our application:

我们已经准备好了,我们可以运行我们的应用程序

mvn jetty:run

Let’s create a couple of products:

让我们创建几个产品。

$ 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
}

.. read them:

…阅读它们。

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

.. update one of them:

…更新其中的一个。

$ curl -X PUT http://localhost:8080/products/1 
  -H 'content-type: application/json' 
  -d '{"name":"Juice"}'
{
    "message" : "Successfully updated product id 1",
    "code" : 200
}

… read the one that we just updated:

…阅读我们刚刚更新的那篇。

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

Finally, we can delete one:

最后,我们可以删除一个。

$ curl -X DELETE http://localhost:8080/products/2
{
    "message" : "Successfully deleted product id 2",
    "code" : 200
}

9. Conclusion

9.结论

JavaLite has a lot of tools to help developers get an application up and running in minutes. However, while basing things on conventions results in a cleaner and simpler code, it takes a while to understand naming and location of classes, packages, and files.

JavaLite有很多工具可以帮助开发者在几分钟内启动并运行一个应用程序。然而,虽然以惯例为基础会使代码更简洁,但要理解类、包和文件的命名和位置需要一段时间。

This was only an introduction to ActiveWeb and ActiveJDBC, find more documentation on their website and look for our products application in the Github project.

这只是对ActiveWeb和ActiveJDBC的介绍,在他们的网站上可以找到更多的文档,并在Github项目上寻找我们的产品应用。