1. 簡介
JavaLite 是一個用於簡化開發者在構建應用程序時需要處理的常見任務的框架集合。在本教程中,我們將重點關注 JavaLite 的功能,重點構建一個簡單的 API。
2. Setup
在整個教程中,我們將創建一個簡單的 RESTful CRUD 應用程序。為此,
所以,讓我們開始,添加第一個所需的依賴項:
<dependency>
<groupId>org.javalite</groupId>
<artifactId>activeweb</artifactId>
<version>1.15</version>
</dependency>
ActiveWeb 構件包含 ActiveJDBC,因此無需單獨添加。請注意,最新 activeweb 版本可以在 Maven Central 上找到。
我們需要第二個依賴項是
<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 上找到。
所有這些都已到位,在開始實體、表和映射之前,我們將確保
現在,我們準備好開始面向關係到對象映射。
3. Object-Relational Mapping
3.1. Mapping and Instrumentation
Let’s get started by
public class Product {}
And, let’s also
CREATE TABLE PRODUCTS (
id int(11) DEFAULT NULL auto_increment PRIMARY KEY,
name VARCHAR(128)
);
Finally, we can
public class Product extends Model {}
We only need to extend
Furthermore, ActiveJDBC automatically recognizes that
There’s one final thing that we will need to make our mapping work: instrumentation.
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
...
[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 主要基於約定,項目結構是其中的一個例子。
src
|----main
|----java.app
| |----config
| |----controllers
| |----models
|----resources
|----webapp
|----WEB-INF
|----views
有一特定的包需要我們重點關注,即
在該包中,我們將創建三個類:
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
這將自動創建連接,替換我們在第一個映射測試的第一行所做的事情。
第二個需要包含在
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) {}
}
在創建三個類之後,關於配置的最後一件事情是
<?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
Inside that directory, we will place our views in a folder called
Let’s create our first template called
{
"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
[<@render partial="product" collection=products/>]
Finally,
@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
Then, as we’re not specifying any view,
In the second method, we’re assigning product
We could also create a view
{
"message" : "${message}",
"code" : ${code}
}
And then call it form any of our
view("message", "There was an error.", "code", 200);
render("message");
Let’s now see our final
@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 擁有許多工具,可以幫助開發者 幾分鐘內啓動應用程序。然而,儘管基於約定結果出更簡潔、更簡單的代碼,但理解類、包和文件的命名和位置需要一些時間。