Contents

ActiveWeb简介

1. 概述

在本文中,我们将说明Activeweb ——来自 JavaLite 的全栈 Web 框架——提供开发动态 Web 应用程序或 REST-ful Web 服务所需的一切。

2. 基本概念与原则

Activeweb 利用“约定优于配置”——这意味着它是可配置的,但具有合理的默认值并且不需要额外的配置。我们只需要遵循一些预定义的约定,例如以某种预定义的格式命名类、方法和字段。

它还通过重新编译并将源重新加载到正在运行的容器(默认为 Jetty)中来简化开发。

对于依赖管理,它使用 Google Guice 作为 DI 框架;要了解有关 Guice 的更多信息,请在此处查看我们的指南

3. Maven 设置

首先,让我们先添加必要的依赖项:

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>activeweb</artifactId>
    <version>3.4-j11</version>
</dependency>

最新版本可以在这里 找到。

此外,为了测试应用程序,我们需要activeweb-testing依赖项:

<dependency>
    <groupId>org.javalite</groupId>
    <artifactId>activeweb-testing</artifactId>
    <version>3.4-j11</version>
    <scope>test</scope>
</dependency>

在这里 查看最新版本。

4. 应用结构

正如我们所讨论的,应用程序结构需要遵循一定的约定;这是典型的 MVC 应用程序的样子:

/uploads/activeweb/1.png

我们可以看到,controllersserviceconfigmodels应该位于app包中各自的子包中。

视图应位于WEB-INF/views目录中,每个视图都有基于控制器名称的自己的子目录。例如app.controllers.ArticleController应该有一个*article/*子目录,其中包含该控制器的所有视图文件。

部署描述符或web.xml通常应包含filter和相应的filter-mapping。由于框架是一个 servlet 过滤器,所以有一个过滤器配置,而不是servlet配置:

...
<filter>
    <filter-name>dispatcher</filter-name>
    <filter-class>org.javalite.activeweb.RequestDispatcher</filter-class>
...
</filter>
...

我们还需要一个init-param来定义应用程序的默认控制器root_controller——类似于home控制器:

...
<init-param>
    <param-name>root_controller</param-name>
    <param-value>home</param-value>
</init-param>
...

5. 控制器

控制器是 ActiveWeb 应用程序的主要组件;并且,如前所述,所有控制器都应位于app.controllers包内:

public class ArticleController extends AppController {
    // ...
}

请注意,控制器正在扩展org.javalite.activeweb.AppController

5.1.控制器 URL 映射

控制器会根据约定自动映射到 URL。例如,ArticleController将被映射到:

http://host:port/contextroot/article

现在,这会将它们映射到控制器中的默认操作。动作只不过是控制器内部的方法。将默认方法命名为index()

public class ArticleController extends AppController {
    // ...
    public void index() {
        render("articles");    
    }
    // ...
}

对于其他方法或操作,请将方法名称附加到 URL:

public class ArticleController extends AppController {
    // ...
    
    public void search() {
        render("search");
    }
}

网址:

http://host:port/contextroot/article/search

我们甚至可以有基于 HTTP 方法的控制器动作。只需使用*@POST、@PUT、@DELETE、@GET、@HEAD *中的任何一个来注释该方法。如果我们不注释一个动作,它默认被认为是一个 GET。

5.2. 控制器 URL 解析

该框架使用控制器名称和子包名称来生成控制器 URL。例如app.controllers.ArticleController.java的 URL:

http://host:port/contextroot/article

如果控制器位于子包中,则 URL 简单地变为:

http://host:port/contextroot/blogdemo/article

对于包含多个单词的控制器名称(例如app.controllers.PublishedArticleController.java),URL 将使用下划线分隔:

http://host:port/contextroot/published_article

5.3. 检索请求参数

在控制器内部,我们可以使用AppController 类的*param()params()*方法访问请求参数。第一个方法接受一个字符串参数——要检索的参数的名称:

public void search() {
    String keyword = param("key");  
    view("search",articleService.search(keyword));
}

如果需要,我们可以使用后者来获取所有参数:

public void search() {
        
    Map<String, String[]> criterion = params();
    // ...
}

6. 意见

在 ActiveWeb 术语中,视图通常称为模板。这主要是因为它使用 Apache FreeMarker 模板引擎而不是 JSP。您可以在我们的指南 中阅读有关 FreeMarker的更多信息。

将模板放在WEB-INF/views目录中。每个控制器都应该有一个按其名称命名的子目录,其中包含它所需的所有模板。

6.1. 控制器视图映射

当一个控制器被点击时,默认的动作index()被执行并且框架将选择WEB-INF/views/article/index.ftl模板作为该控制器的视图目录。同样,对于任何其他操作,将根据操作名称选择视图。

这并不总是我们想要的。有时我们可能希望根据内部业务逻辑返回一些视图。在这种情况下,*我们可以使用父类org.javalite.activeweb.AppController类的*render()方法来控制进程:

public void index() {
    render("articles");    
}

请注意,自定义视图的位置也应该在该控制器的同一视图目录中。如果不是这种情况,则在模板名称前面加上模板所在的目录名称,并将其传递给*render()*方法:

render("/common/error");

6.3. 数据视图

为了向视图发送数据,org.javalite.activeweb.AppController提供了*view()*方法:

view("articles", articleService.getArticles());

这需要两个参数。首先,用于访问模板中对象的对象名称,其次是包含数据的对象。

我们还可以使用*assign()*方法将数据传递给视图。view() 和 *assign()*方法之间绝对没有区别——我们可以选择其中任何一种:

assign("article", articleService.search(keyword));

让我们映射模板中的数据:

<@content for="title">Articles</@content>
...
<#list articles as article>
    <tr>
        <td>${article.title}</td>
        <td>${article.author}</td>
        <td>${article.words}</td>
        <td>${article.date}</td>
    </tr>
</#list>

7. 管理依赖

为了管理对象和实例,ActiveWeb 使用 Google Guice 作为依赖管理框架。

假设我们的应用程序中需要一个服务类;这会将业务逻辑与控制器分开。

我们先创建一个服务接口:

public interface ArticleService {
    
    List<Article> getArticles();   
    Article search(String keyword);
    
}

和实施:

public class ArticleServiceImpl implements ArticleService {
    public List<Article> getArticles() {
        return fetchArticles();
    }
    public Article search(String keyword) {
        Article ar = new Article();
        ar.set("title", "Article with "+keyword);
        ar.set("author", "blogdemo");
        ar.set("words", "1250");
        ar.setDate("date", Instant.now());
        return ar;
    }
}

现在,让我们将此服务绑定为 Guice 模块:

public class ArticleServiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(ArticleService.class).to(ArticleServiceImpl.class)
          .asEagerSingleton();
    }
}

最后,在应用程序上下文中注册它并根据需要将其注入控制器:

public class AppBootstrap extends Bootstrap {
    public void init(AppContext context) {
    }
    public Injector getInjector() {
        return Guice.createInjector(new ArticleServiceModule());
    }
}

请注意,此配置类名称必须是AppBootstrap,并且它应该位于app.config包中。

最后,这是我们将其注入控制器的方法:

@Inject
private ArticleService articleService;

8. 测试

ActiveWeb 应用程序的单元测试是使用 JavaLite 的JSpec 库编写的。

我们将使用 JSpec 中的org.javalite.activeweb.ControllerSpec类来测试我们的控制器,我们将按照类似的约定命名测试类:

public class ArticleControllerSpec extends ControllerSpec {
    // ...
}

请注意,该名称类似于它正在测试的控制器,末尾带有“Spec”。

这是测试用例:

@Test
public void whenReturnedArticlesThenCorrect() {
    request().get("index");
    a(responseContent())
      .shouldContain("<td>Introduction to Mule</td>");
}

请注意,*request()方法模拟了对控制器的调用,而相应的 HTTP 方法get()*将操作名称作为参数。

我们还可以使用*params()*方法将参数传递给控制器:

@Test
public void givenKeywordWhenFoundArticleThenCorrect() {
    request().param("key", "Java").get("search");
    a(responseContent())
      .shouldContain("<td>Article with Java</td>");
}

要传递多个参数,我们也可以使用这个流畅的 API 链接方法。

9. 部署应用程序

可以将应用程序部署在任何 servlet 容器中,例如 Tomcat、WildFly 或 Jetty。当然,部署和测试最简单的方法是使用 Maven Jetty 插件:

...
<plugin>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-maven-plugin</artifactId>
    <version>9.4.8.v20171121</version>
    <configuration>
        <reload>manual</reload>
        <scanIntervalSeconds>10000</scanIntervalSeconds>
    </configuration>
</plugin>
...

最新版本的插件在这里

现在,终于 - 我们可以启动它了:

mvn jetty:run