├── cn ├── i18n.md ├── gen_pdf.sh ├── model.md ├── multi_db.md ├── aaa.md ├── lombok.md ├── index.md ├── recipe │ ├── di-binding.md │ ├── job-schedule.md │ ├── file-upload.md │ ├── user-password-hash.md │ ├── singleton.md │ ├── json-response.md │ ├── resource_loading.md │ └── di-inject-type.md ├── event.md ├── di.md ├── releases │ ├── r1.4.0.md │ └── r1.2.0.md ├── hibernate.md ├── reference │ ├── session_flash.md │ ├── bootstrap.md │ └── builtin-handler.md ├── job.md ├── cli.md ├── email.md ├── social_link.md ├── faq.md ├── templating.md ├── ebean.md ├── techempower │ └── r14.md ├── morphia.md ├── act-core-dependencies.md ├── websocket.md └── interceptor.md ├── processor ├── src │ └── main │ │ ├── resources │ │ ├── tags │ │ │ ├── new-page.tag │ │ │ ├── wip.tag │ │ │ └── meta-body.tag │ │ ├── chapters.list │ │ ├── actdoc │ │ │ └── .version │ │ ├── conf │ │ │ ├── uat │ │ │ │ └── app.properties │ │ │ └── prod │ │ │ │ └── app.properties │ │ └── logback.xml │ │ └── java │ │ └── actdoc │ │ ├── Tags.java │ │ ├── Book.java │ │ └── Processor.java ├── run_dev.bat ├── run_e2e.bat ├── run_dev ├── run_e2e ├── run_prod ├── .gitignore ├── README.md └── pom.xml ├── img ├── lombok │ ├── IDEA.png │ └── lombokJar.png └── getting_start │ ├── bye_in_browser.png │ ├── idea_run_app.png │ ├── idea_import_step1.png │ ├── idea_import_step2.png │ ├── idea_import_step3.png │ ├── idea_prj_first_view.png │ ├── idea_rythm_sayhello.png │ ├── helloworld_in_browser.png │ ├── helloworld2_in_browser.png │ ├── helloworld3_in_browser.png │ ├── simplify_controller_util_1.png │ └── simplify_controller_util_2.png ├── README.md ├── .gitignore └── en ├── model.md ├── multi_db.md ├── deploy.md ├── index.md ├── recipe ├── di-binding.md ├── file-upload.md ├── user-password-hash.md ├── job-schedule.md ├── singleton.md ├── json-response.md └── di-inject-type.md ├── event.md ├── di.md ├── job.md ├── releases └── r1.4.0.md ├── routing.md ├── cli.md ├── faq.md ├── email.md ├── act-core-dependencies.md ├── templating.md ├── techempower └── r14.md ├── ebean.md └── morphia.md /cn/i18n.md: -------------------------------------------------------------------------------- 1 | # ActFramework 国际化 2 | 3 | TBD 4 | -------------------------------------------------------------------------------- /processor/src/main/resources/tags/new-page.tag: -------------------------------------------------------------------------------- 1 | \newpage -------------------------------------------------------------------------------- /processor/src/main/resources/tags/wip.tag: -------------------------------------------------------------------------------- 1 | \SetWatermarkText{WIP} -------------------------------------------------------------------------------- /processor/run_dev.bat: -------------------------------------------------------------------------------- 1 | echo building ... 2 | mvn -q compile act:run -------------------------------------------------------------------------------- /processor/run_e2e.bat: -------------------------------------------------------------------------------- 1 | echo building ... 2 | mvn -q compile act:e2e -------------------------------------------------------------------------------- /processor/run_dev: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo building ... 3 | mvn -q compile act:run -------------------------------------------------------------------------------- /processor/run_e2e: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | echo building ... 3 | mvn -q compile act:e2e -------------------------------------------------------------------------------- /img/lombok/IDEA.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/lombok/IDEA.png -------------------------------------------------------------------------------- /img/lombok/lombokJar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/lombok/lombokJar.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The ActFramework Document Project 2 | 3 | - [English Version](en/index.md) 4 | - [中文版本](cn/index.md) 5 | -------------------------------------------------------------------------------- /processor/src/main/resources/chapters.list: -------------------------------------------------------------------------------- 1 | get_start.md 2 | configuration.md 3 | di.md 4 | routing.md 5 | controller.md -------------------------------------------------------------------------------- /img/getting_start/bye_in_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/bye_in_browser.png -------------------------------------------------------------------------------- /img/getting_start/idea_run_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_run_app.png -------------------------------------------------------------------------------- /processor/src/main/resources/actdoc/.version: -------------------------------------------------------------------------------- 1 | artifact=${project.artifactId} 2 | version=${project.version} 3 | build=${buildNumber} -------------------------------------------------------------------------------- /img/getting_start/idea_import_step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_import_step1.png -------------------------------------------------------------------------------- /img/getting_start/idea_import_step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_import_step2.png -------------------------------------------------------------------------------- /img/getting_start/idea_import_step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_import_step3.png -------------------------------------------------------------------------------- /img/getting_start/idea_prj_first_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_prj_first_view.png -------------------------------------------------------------------------------- /img/getting_start/idea_rythm_sayhello.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/idea_rythm_sayhello.png -------------------------------------------------------------------------------- /img/getting_start/helloworld_in_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/helloworld_in_browser.png -------------------------------------------------------------------------------- /img/getting_start/helloworld2_in_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/helloworld2_in_browser.png -------------------------------------------------------------------------------- /img/getting_start/helloworld3_in_browser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/helloworld3_in_browser.png -------------------------------------------------------------------------------- /img/getting_start/simplify_controller_util_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/simplify_controller_util_1.png -------------------------------------------------------------------------------- /img/getting_start/simplify_controller_util_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/actframework/act-doc/HEAD/img/getting_start/simplify_controller_util_2.png -------------------------------------------------------------------------------- /processor/src/main/resources/tags/meta-body.tag: -------------------------------------------------------------------------------- 1 | --- 2 | header-includes: 3 | - \usepackage{draftwatermark} 4 | output: 5 | pdf_document: 6 | keep_tex: yes 7 | --- -------------------------------------------------------------------------------- /processor/run_prod: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ ! -f target/dist/start ]; then 3 | echo building ... 4 | mvn -q clean package 5 | cd target/dist 6 | tar xzf *.tar.gz 7 | else 8 | cd target/dist 9 | fi 10 | ./run $* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | .classpath 3 | .project 4 | .settings 5 | .vscode 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | lib 11 | target 12 | .idea 13 | classes 14 | *.iml 15 | deploy 16 | **/.act* 17 | git.log 18 | tmp/ 19 | *.geany 20 | -------------------------------------------------------------------------------- /processor/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | target 3 | .idea 4 | classes 5 | *.log 6 | *.DS_Store 7 | *all.sql 8 | tmp/ 9 | **/.act* 10 | **/.classpath 11 | **/.settings 12 | **/.project 13 | **/.settings/ 14 | store1/ 15 | test.mv.db 16 | test.trace.db 17 | act.pid 18 | .workspace 19 | *.geany 20 | -------------------------------------------------------------------------------- /processor/src/main/resources/conf/uat/app.properties: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # Application configuration for uat profile 3 | # act-1.8.8-RC11 4 | ############################################## 5 | act.secret=NY2yNyjF8sePodbgyJaxBAx6YP0ytyV9rYM5b7iglRMWXNhAwnPpFWlWMZ75xwnj -------------------------------------------------------------------------------- /processor/src/main/resources/conf/prod/app.properties: -------------------------------------------------------------------------------- 1 | ############################################## 2 | # Application configuration for prod profile 3 | # act-1.8.8-RC11 4 | ############################################## 5 | act.secret=XdAByDWAhEsUv8SgsyBhXhO7qqtuZ51B4kq0RLwWTGdFvkWSM48Nwv5KnUdmbi3f -------------------------------------------------------------------------------- /cn/gen_pdf.sh: -------------------------------------------------------------------------------- 1 | pandoc --latex-engine=xelatex -V mainfont='WenQuanYi Micro Hei' -V monofont='WenQuanYi Micro Hei Mono' -s -V geometry:margin=0.5in -f markdown-implicit_figures -o act-1.8.7.pdf index.md get_start.md configuration.md di.md routing.md controller.md reference/bootstrap.md reference/builtin-handler.md reference/session_flash.md 2 | -------------------------------------------------------------------------------- /cn/model.md: -------------------------------------------------------------------------------- 1 | # 域模型和数据库访问 2 | 3 | ActFramework不限制应用程序使用数据库访问机制,同时也提供了推荐的数据访问框架: 4 | 5 | 1. 管理数据访问的配置 6 | 1. 一个简单易用的数据访问对象接口 7 | 1. 多数据库访问支持 8 | 9 | ActFramework目前支持使用[Morphia](http://mongodb.github.io/morphia/)访问MongoDB, 以及[EBean](http://ebean-orm.github.io/),JPA等方式访问SQL数据库. 10 | 11 | **Act支持的ORM框架有:** 12 | 13 | 面向Sql数据库的 14 | 15 | 1. [Ebean](ebean.md) 16 | 1. [Hibernate](hibernate.md) 17 | 1. Eclipse-link 18 | 1. BeetlSQL 19 | 20 | 面向NoSQL数据库的 21 | 22 | 1.[Morphia](morphia.md) 23 | 24 | **开发计划** 25 | 1. Act-Redis 26 | 1. Act-Mybatis 27 | 28 | 参考: 29 | 30 | 1. [开发多数据源应用](multi_db.md) 31 | 32 | 33 | [回到目录](index.md) -------------------------------------------------------------------------------- /processor/src/main/java/actdoc/Tags.java: -------------------------------------------------------------------------------- 1 | package actdoc; 2 | 3 | import act.inject.util.LoadResource; 4 | import org.osgl.util.Keyword; 5 | import org.osgl.util.S; 6 | 7 | import java.util.Map; 8 | import javax.inject.Singleton; 9 | 10 | @Singleton 11 | public class Tags { 12 | 13 | @LoadResource("tags") 14 | private Map mapping; 15 | 16 | public String substitude(String line) { 17 | String key = line; 18 | if (line.startsWith("<")) { 19 | key = S.ensureStrippedOff(line, S.ANGLE_BRACKETS); 20 | } 21 | String substitute = mapping.get(Keyword.of(key)); 22 | return null == substitute ? line : substitute; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /processor/README.md: -------------------------------------------------------------------------------- 1 | # Act Document Processor 2 | 3 | The application process ActFramework document markdown source and generate the final PDF book. 4 | 5 | 1. Process tag substitution 6 | In order to hide TeX instructions from the markdown, so we have created special tags with each one represent a specific TeX instruction. Before we generating the PDF book, we need to substitute the tags with TeX instructions. 7 | 2. Concatenate multiple markdown source files into single file 8 | 9 | ## Tags substitution 10 | 11 | ### Meta block 12 | 13 | `` mapped to the following code 14 | 15 | ``` 16 | --- 17 | header-includes: 18 | - \usepackage{draftwatermark} 19 | output: 20 | pdf_document: 21 | keep_tex: yes 22 | --- 23 | ``` 24 | 25 | ### Other tags 26 | 27 | * `` mapped to `\SetWatermarkText{WIP}` 28 | * `` mapped to `\newpage` 29 | -------------------------------------------------------------------------------- /en/model.md: -------------------------------------------------------------------------------- 1 | # Model and Database Access 2 | 3 | The application developer can choose whatever model and database access technology they want to use in ActFramework. However Act provides a recommended set of Model/DAO mechanism that help developer with: 4 | 5 | 1. DB configuration 6 | 1. A simple and easy to use DAO interface 7 | 1. Multi DB access support 8 | 9 | At the moment ACT support accessing MongoDB with [Morphia](http://mongodb.github.io/morphia/) and SQL database with [EBean](http://ebean-orm.github.io/). 10 | 11 | **Note** JPA is not supported within ActFramework Model framework at the moment. However you are free to use JPA/Hibernate give you manage all the configurations and initializations 12 | 13 | Read: 14 | 15 | 1. [Access MongoDB with Morphia](morphia.md) 16 | 1. [Access SQL database with EBean](ebean.md) 17 | 1. [Multi-DB application](multi_db.md) 18 | 19 | [Back to index](index.md) -------------------------------------------------------------------------------- /processor/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | act.doc 8 | processor 9 | 1.0-SNAPSHOT 10 | 11 | Act Documents Processor 12 | 13 | 14 | org.actframework 15 | act-starter-parent 16 | 1.8.8.7-SNAPSHOT 17 | 18 | 19 | 20 | 21 | 1.8 22 | actdoc.Processor 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cn/multi_db.md: -------------------------------------------------------------------------------- 1 | # 多数据源应用 2 | 3 | ActFramework支持在应用中使用多个数据源 4 | 5 | ## 配置 6 | 7 | ``` 8 | db.instances=db1,db2 9 | 10 | # db1 configurations 11 | db.db1.impl=act.db.morphia.MorphiaPlugin 12 | db.db1.uri=mongodb://localhost/test 13 | 14 | db.db2.impl=act.db.ebean.EbeanPlugin 15 | db.db2.driver=org.h2.Driver 16 | db.db2.url=jdbc:h2:mem:test 17 | ``` 18 | 19 | 上面的配置指定了连个数据源 20 | 21 | 1. db1, 第一个也是默认数据源, 是一个mongodb连接,访问`localhost/test` 22 | 1. db2, 是一个ebean连接,使用h2 jdbc驱动连接到名字为`test`的内存数据库 23 | 24 | ## 在域模型类中指定数据源 25 | 26 | 下面的`Blog`模型没有特定指定数据源,因此会使用默认的数据源, 在以上配置中是`db1` 27 | 28 | ```java 29 | package com.mycom.myprj; 30 | 31 | import org.mongodb.morphia.annotations.Entity; 32 | import act.db.morphia.MorphiaModel; 33 | 34 | @Entity("blog") 35 | public class Blog extends MorphiaModel { 36 | ... 37 | } 38 | ``` 39 | 下面的`Account`模型通过`@DB`z注解指定了使用`db2`数据源: 40 | 41 | ```java 42 | import act.db.DB; 43 | 44 | import javax.persistence.Entity; 45 | import javax.persistence.Id; 46 | 47 | @DB("db2") 48 | @Entity(name = "acc") 49 | public class Account { 50 | ... 51 | } 52 | ``` 53 | 54 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/aaa.md: -------------------------------------------------------------------------------- 1 | # 第九章 认证授权和记账 2 | 3 | 绝大多数 Web 应用都免不了对访问用户的认证及其与系统交互操作的授权. 很多正式 (Non-trivial) 的系统都会将用户的操作记录下来,以备后查. 我们把系统的这三个功能简称为 AAA (Authentication, Authorisation, Accounting). 4 | 5 | ActFramework 通过 [act-aaa](https://github.com/actframework/act-aaa-plugin) 插件为应用提供 AAA 的实现框架. 6 | 7 | ## 9.1 介绍 8 | 9 | act-aaa 插件为 ActFramework 应用程序提供认证,授权以及记账的框架与工具. 和现有的安全工具包括 [Apache Shiro](https://shiro.apache.org/) 以及 [Spring Security](https://spring.io/projects/spring-security) 相比, act-aaa-plugin 提供了更为简便的集成, 面向资源的授权, 内置行级别权限分辨以及自动记账功能. 10 | 11 | ## 9.2 认证 12 | 13 | ### 9.2.1 认证主体 14 | 15 | 认证主体 (Principal) 在认证过程中代表系统中定义好的用户帐户. 认证主体携带一下信息: 16 | 17 | 1. username - 用户名 18 | 2. password - 密码 19 | 3. roles - 该用户的角色集合 20 | 4. permissions - 该用户的权限集合 21 | 5. privilege - 该用户的特权级别 22 | 23 | 通常情况下使用用户名和密码来确认某个登录会话的认证主体是否存在系统中. 24 | 25 | act-aaa 提供了 `UserBase` 类 (集成 act-morphia 的应用则使用 `MorphiaUserBase` 类) 帮助应用定义自己的用户类以提供认证主题 (Principal). 26 | 27 | ## 9.3 授权 28 | 29 | ## 9.4 记账 30 | 31 | ## 9.5 高级课题 32 | 33 | ## 9.5.1 引入其他认证机制 34 | 35 | ## 9.5.2 自定义 TBD 36 | -------------------------------------------------------------------------------- /cn/lombok.md: -------------------------------------------------------------------------------- 1 | # 与 Lombok 集成。 2 | 3 | ## 为什么 ActFramework 与 Lombok 集成略显复杂? 4 | 5 | ActFramework 拥有运行时重载的特性,即在开发模式下,代码变动,框架能自动载入修改后的代码。 6 | 但正常情况下 Ecj 编译器并不能对 Lombok 产生支持,所以我们要通过特殊手段让 Ecj 能响应 Lombok。 7 | 8 | 这种手段,就是 JavaAgent。 9 | 10 | Lombok 本身提供了对 Ecj 的支持,所以我们只需要把 Lombok 配置到 JavaAgent 就好了。 11 | 12 | ## 如何与 Lombok 集成 13 | 14 | ### 开发环境 15 | 16 | 首先,我们需要引入 Lombok 依赖。 17 | 18 | 19 | org.projectlombok 20 | lombok 21 | 1.16.10 22 | 23 | 24 | 你可以按需引入高版本,但高版本可能由于某些原因,导致 ActFramework 无法正常启动。 25 | 26 | 引入依赖后,我们需要找到 Lombok 的位置,并配置好 JavaAgent。 27 | 这里我推荐将 Lombok 的 jar 复制到项目目录,并上传到版本控制服务。 28 | 29 | ![image](/img/lombok/lombokJar.png) 30 | 31 | 然后,在我们的 IDEA 启动项中配置好 JavaAgent 参数。 32 | -javaagent:lib/lombok.jar=ECJ 33 | 34 | ![image](/img/lombok/IDEA.png) 35 | 36 | 如果你做好了这两步,那么你可以尝试运行,这时候 Lombok 便可以正常的使用了。 37 | 38 | ### 打包运行 39 | 由于 ActFramework 在打包过程中要进行测试。 40 | 测试时也会启动 ActFramework 并编译相关 Class 文件。 41 | 所以我们也应该在这个过程中配置 JavaAgent 以支持 Lombok。 42 | 这个时候,我们只需要在 pom.xml 的 properties 节点中,加入 act.lombok 属性,并指向 Lombok 的路径。 43 | 44 | 45 | lib/lombok.jar 46 | 47 | 48 | 这样,打包测试的时候,我们就能正常的使用 Lombok 了。 -------------------------------------------------------------------------------- /en/multi_db.md: -------------------------------------------------------------------------------- 1 | # Multi DB Application 2 | 3 | ActFramework provides support to multiple data sources in your application. 4 | 5 | ## Configuration 6 | 7 | ``` 8 | db.instances=db1,db2 9 | 10 | # db1 configurations 11 | db.db1.impl=act.db.morphia.MorphiaPlugin 12 | db.db1.uri=mongodb://localhost/test 13 | 14 | db.db2.impl=act.db.ebean.EbeanPlugin 15 | db.db2.driver=org.h2.Driver 16 | db.db2.url=jdbc:h2:mem:test 17 | ``` 18 | 19 | As per above configuration, two database sources will be configured: 20 | 21 | 1. db1, also the default datasource, is a mongodb connection to `localhost/test` 22 | 1. db2, is a ebean connection using h2 jdbc driver connecting to in memory databse named `test` 23 | 24 | ## Specify database in Model class 25 | 26 | The `Blog` model will use the default db, which is `db1` 27 | 28 | ```java 29 | package com.mycom.myprj; 30 | 31 | import org.mongodb.morphia.annotations.Entity; 32 | import act.db.morphia.MorphiaModel; 33 | 34 | @Entity("blog") 35 | public class Blog extends MorphiaModel { 36 | ... 37 | } 38 | ``` 39 | The `Account` model will use the `db2` data source: 40 | 41 | ```java 42 | import act.db.DB; 43 | 44 | import javax.persistence.Entity; 45 | import javax.persistence.Id; 46 | 47 | @DB("db2") 48 | @Entity(name = "acc") 49 | public class Account { 50 | ... 51 | } 52 | ``` 53 | 54 | [Back to index](index.md) -------------------------------------------------------------------------------- /en/deploy.md: -------------------------------------------------------------------------------- 1 | # Deploy and run your ActFramework application 2 | 3 | ActFramework app does not require servlet container/server to start. So deployment is very simple: 4 | 5 | 1. Make sure yous application's `pom.xml` file follow the [sample `pom.xml`](https://github.com/actframework/act-demo-apps/blob/master/helloworld/pom.xml) 6 | 7 | 2. Make sure your application's project contains the `src/assembly` folder copied from the [sample project](https://github.com/actframework/act-demo-apps/tree/master/helloworld/src/assembly) 8 | 9 | 3. run `mvn clean package` command, and then you should be able to find a zip file in your `target/dist` dir 10 | 11 | 4. scp the zip file to your product server 12 | 13 | 5. ssh to your product server, locate the zip file uploaded, unzip it 14 | 15 | 6. type `./run` to run in current process, or `./start` to run in background 16 | 17 | Normally you should have a frontend http server, e.g. nginx to service the request especially if you have multiple applications run in the same box. Here is one nginx configuration example: 18 | 19 | ``` 20 | server { 21 | listen 80; 22 | server_name myapp.mycom.com; 23 | location / { 24 | proxy_pass http://localhost:5460; 25 | proxy_set_header X-Real-IP $remote_addr; 26 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 27 | proxy_set_header Host $http_host; 28 | } 29 | } 30 | ``` 31 | -------------------------------------------------------------------------------- /processor/src/main/java/actdoc/Book.java: -------------------------------------------------------------------------------- 1 | package actdoc; 2 | 3 | import act.app.conf.AutoConfig; 4 | import act.util.SimpleBean; 5 | import org.osgl.util.C; 6 | import org.osgl.util.IO; 7 | import org.osgl.util.S; 8 | 9 | import java.io.File; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @AutoConfig 14 | public class Book implements SimpleBean { 15 | 16 | public File base; 17 | private Processor processor; 18 | private String lang; 19 | 20 | public Book(String lang, Processor processor) { 21 | this.base = processor.base(lang); 22 | this.processor = processor; 23 | this.lang = lang; 24 | } 25 | 26 | public void process() { 27 | List lines = new ArrayList<>(); 28 | for (String chapter : processor.chapters()) { 29 | File src = new File(base, chapter); 30 | List fileLines = IO.readLines(src); 31 | C.List processedFileLines = C.newList(); 32 | for (String line : fileLines) { 33 | if ("[返回目录](index.md)".equals(line.trim())) { 34 | continue; 35 | } 36 | line = processor.processTag(line); 37 | processedFileLines.add(line); 38 | } 39 | if (!lines.isEmpty()) { 40 | processedFileLines.add("\\newpage"); 41 | } 42 | lines.addAll(processedFileLines); 43 | } 44 | File target = new File(processor.workspace(), "act_doc-" + lang + ".md"); 45 | IO.write(S.join(lines).by("\n").get()).to(target); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /cn/index.md: -------------------------------------------------------------------------------- 1 | # ActFramework 文档 - r1.8.21.0 2 | 3 | ActFramework 是一款[高性能 Java 全栈框架](https://www.techempower.com/benchmarks/#section=data-r15&hw=cl&test=fortune&l=hra0e7&c=4&o=4),用于开发传统的 MVC 应用或 RESTful 服务。和其他现有 MVC/RESTful 框架相比,ActFramework 的优势在于表达力和简洁易用。 4 | 5 | 6 | **注:文档链接括号内的版本为文档的最后更新版本,如无特殊注明则代表到目前该文档内容依旧适用。** 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ## 基本概念 15 | 16 | 1. [入门(r1.8.21.0)](get_start.md) 17 | 1. [配置(r1.8.8)](configuration.md) 18 | 1. [依赖注入(r1.8.8)](di.md) 19 | 1. [路由(r1.8.21)](routing.md) 20 | 1. [控制器(r1.8.8)](controller.md) 21 | 1. [域模型和数据访问(r1.8.21)](model.md) 22 | 1. [模板(r1.8.8)](templating.md) 23 | 24 | ## 工具 25 | 26 | 1. [测试(r1.8.8)](test.md) 27 | 1. [认证授权与记账(r1.8.8)](aaa.md) 28 | 1. [从社交服务认证(r1.8.8)](social_link.md) 29 | 30 | ## 高级课题 31 | 32 | 1. [拦截器(r1.8.8)](interceptor.md) 33 | 1. [作业调度(r1.8.8)](job.md) 34 | 1. [事件绑定与分发(r1.8.8)](event.md) 35 | 1. [发送邮件(r1.8.8)](email.md) 36 | 1. [创建命令行程序(r1.8.8)](cli.md) 37 | 1. [WebSocket支持(r1.8.8)](websocket.md) 38 | 1. Using CLI to inspect and manage your application 39 | 1. Using act-aaa to implement security 40 | 1. Using act-storage to implement file persistence 41 | 42 | ## 小灶 43 | 44 | 1. [控制JSON响应字段(r1.8.8)](recipe/json-response.md) 45 | 1. [处理文件上传(r1.8.8)](recipe/file-upload.md) 46 | 1. [任务调度(r1.8.8)](recipe/job-schedule.md) 47 | 1. [ActFramework中使用单例(r1.8.8)](recipe/singleton.md) 48 | 1. [依赖注入 - 注入对象类型(r1.8.8)](recipe/di-inject-type.md) 49 | 1. [用户密码的存储与验证机制与应用(r1.8.8)](recipe/user-password-hash.md) 50 | 1. [依赖注入III - 自定义绑定(r1.8.8)](recipe/di-binding.md) 51 | 1. [加载资源文件(r1.8.8)](recipe/resource_loading.md) 52 | 53 | ## 参考手册 54 | 55 | 1. [配置(r1.8.8)](configuration.md) 56 | 57 | ## 与其他工具集成 58 | 1. [Lombok](lombok.md) 59 | 60 | 61 | -------------------------------------------------------------------------------- /processor/src/main/java/actdoc/Processor.java: -------------------------------------------------------------------------------- 1 | package actdoc; 2 | 3 | import act.Act; 4 | import act.cli.Command; 5 | import act.cli.Required; 6 | import act.inject.DefaultValue; 7 | import act.inject.util.LoadResource; 8 | import act.job.OnAppStart; 9 | import org.osgl.exception.UnexpectedException; 10 | import org.osgl.inject.annotation.Configuration; 11 | 12 | import java.io.File; 13 | import java.util.List; 14 | import javax.inject.Inject; 15 | 16 | @SuppressWarnings("unused") 17 | public class Processor { 18 | 19 | @Configuration("doc.base") 20 | private String base; 21 | 22 | @Configuration("workspace") 23 | private String workspace; 24 | 25 | @LoadResource("chapters.list") 26 | private List chapters; 27 | 28 | @Inject 29 | private Tags tags; 30 | 31 | public File base(String lang) { 32 | return new File(base, lang); 33 | } 34 | 35 | public File workspace() { 36 | return new File(workspace); 37 | } 38 | 39 | public List chapters() { 40 | return chapters; 41 | } 42 | 43 | public String processTag(String line) { 44 | return tags.substitude(line); 45 | } 46 | 47 | @OnAppStart 48 | public void ensureWorkspace() { 49 | File file = workspace(); 50 | if (!file.exists()) { 51 | if (!file.mkdir()) { 52 | throw new UnexpectedException("Workspace not ready for use: " + file.getAbsolutePath()); 53 | } 54 | } 55 | } 56 | 57 | @Command(name = "process", help = "process a file") 58 | public void process(@Required("specify language suffix") @DefaultValue("cn") String lang) { 59 | new Book(lang, this).process(); 60 | } 61 | 62 | public static void main(String[] args) throws Exception { 63 | Act.start(); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /en/index.md: -------------------------------------------------------------------------------- 1 | # ActFramework Documentation 2 | 3 | 4 | ## Overview 5 | 6 | ActFramework is created to make programming MVC application in Java easy and fun. In comparing with other Java MVC frameworks like Spring MVC, Struts etc, Act focus more on expressiveness and simplicity. 7 | 8 | ## FAQ 9 | 10 | 1. [FAQ](faq.md) 11 | 12 | ## Getting Started 13 | 14 | 1. [Prerequisites](get_start.md#prerequisites) 15 | 1. [Create the "Hello world" application](get_start.md#create_hello_world_app) 16 | 1. [The anatomy of a Act application](get_start.md#anatomy) 17 | 18 | ## Basic Concepts 19 | 20 | 1. [Controller](controller.md) 21 | 1. [Routing](routing.md) 22 | 1. [Model and DAO](model.md) 23 | 1. [Templating](templating.md) 24 | 1. [Configuration](configuration.md) 25 | 26 | ## Advanced Topics 27 | 28 | 1. [Depedency injection](di.md) 29 | 1. [Interceptors](interceptor.md) 30 | 1. [Jobs and schedulers](job.md) 31 | 1. [Events](event.md) 32 | 1. [Sending email](email.md) 33 | 1. [Creating CLI commander](cli.md) 34 | 1. [WebSocket Support](websocket.md) 35 | 1. Using CLI to inspect and manage your application 36 | 1. Using act-aaa to implement security 37 | 1. Using act-storage to implement file persistence 38 | 39 | ## Recipe 40 | 41 | 1. [Managing JSON response fields](recipe/json-response.md) 42 | 1. [File upload in Actframework](recipe/file-upload.md) 43 | 1. [Job scheduling in ActFramework](recipe/job-schedule.md) 44 | 1. [Singleton in ActFramework](recipe/singleton.md) 45 | 1. [Dependency Injection II - inject object types](recipe/di-inject-type.md) 46 | 1. [Secure user password in ActFramework app](recipe/user-password-hash.md) 47 | 1. [Dependency Injection III - defining bindins](recipe/di-binding.md) 48 | 1. [Deploy and run your application](deploy.md) 49 | 50 | ## References 51 | 52 | 1. [Configuration](reference/configuration.md) 53 | 1. [API doc](https://www.javadoc.io/doc/org.actframework/act/1.8.22) 54 | -------------------------------------------------------------------------------- /cn/recipe/di-binding.md: -------------------------------------------------------------------------------- 1 | # ActFramework 依赖注入 III - 定义绑定 2 | 3 | 在[ActFramework 依赖注入 II - 注入对象类型](di-inject-type.md)中我们提到了定义绑定的一种方式: 4 | 5 | ## 1. 使用Module 6 | 7 | ```java 8 | // Define bindings 9 | public class MyModule extends org.osgl.inject.Module { 10 | protected void configure() { 11 | bind(MyService.class).to(OneService.class); 12 | bind(MyService.class).named("two").to(TwoService.class); 13 | } 14 | } 15 | ``` 16 | 17 | 这篇文章继续介绍ActFramework的其他绑定方式 18 | 19 | ## 2. 自定义工厂 20 | 21 | 工厂和上面的Module是相当的, 把上面的Module用工厂的方式来写会是这样: 22 | 23 | ```java 24 | public class MyFactory { 25 | 26 |    @org.osgl.inject.annotation.Provides 27 | public MyService getOneService(OneService oneService) { 28 | return oneService; 29 | } 30 | 31 | @org.osgl.inject.annotation.Provides 32 | @Named("two") 33 | public MyService getTwoService(TwoService twoService) { 34 | return twoService; 35 | } 36 | 37 | } 38 | ``` 39 | 40 | ## 3. 自动绑定 41 | 42 | 自动绑定不需要定义Module和工厂,但是需要在Interface(被绑定类)上使用`@AutoBind`注解: 43 | 44 | ```java 45 | // The interface 46 | @act.inject.AutoBind 47 | public interface MyService { 48 | void service(); 49 | } 50 | ``` 51 | 52 | 定义缺省实现 53 | 54 | ```java 55 | // The implemention one 56 | public class OneService implements MyService { 57 | public void service() {Act.LOGGER.info("ONE is servicing");} 58 | } 59 | ``` 60 | 61 | 使用`@javax.inject.Named`注解定义Qualified的实现 62 | 63 | ```java 64 | // The implemention two 65 | @javax.inject.Named("two") 66 | public class TwoService implements MyService { 67 | public void service() {Act.LOGGER.info("TWO is servicing");} 68 | } 69 | ``` 70 | 71 | 使用依赖注入 72 | 73 | ```java 74 | // Inject the service 75 | public class Serviced { 76 | 77 | // this one will get bind to the default implementation: OneService 78 | @javax.inject.Inject 79 | private MyService one; 80 | 81 | // this one will get bind to TwoService 82 | @javax.inject.Inject 83 | @javax.inject.Named("two") 84 | private MyService two; 85 | } 86 | ``` 87 | 88 | ## 链接 89 | 90 | * [ActFramework文档主页](../index) 91 | -------------------------------------------------------------------------------- /cn/event.md: -------------------------------------------------------------------------------- 1 | # 事件 2 | 3 | ActFramework提供简单易用的事件绑定和分派机制 4 | 5 | ## 简单事件框架 6 | 7 | 简单事件让开发人员直接使用字串来定义事件。而事件响应方法则是一个有`act.event.On`注解标注的普通Java方法。分派简单事件可以传入任何参数,这些参数都将传入事件响应方法。 8 | 9 | ### 申明事件响应方法 10 | 11 | ```java 12 | public class Foo { 13 | @On(value = "customer-created", async = true) 14 | public void sendWelcomeEmail(Contact newCustomer) { 15 | ... 16 | } 17 | } 18 | ``` 19 | 20 | ### 触发事件 21 | 22 | ```java 23 | @Controller("/customer") 24 | public class CustomerController { 25 | 26 | @PostAction("/") 27 | public void createCustomer(Customer customer, @Context EventBus eventBus) { 28 | customerDao.save(customer); 29 | eventBus.trigger("customer-created", customer); 30 | } 31 | } 32 | ``` 33 | 34 | ## 类型安全事件框架 35 | 36 | 类型安全事件框架实现更加传统的事件绑定和分派机制 37 | 38 | 39 | ### 申明事件响应方法 40 | 41 | ```java 42 | import act.event.ActEvent; 43 | import act.event.ActEventListenerBase; 44 | import act.util.Async; 45 | 46 | @Async 47 | public class CustomerCreated extends ActEventListenerBase> { 48 | @Override 49 | public void on(ActEvent event) throws Exception { 50 | Customer customer = event.source(); 51 | // send welcome email to customer 52 | } 53 | } 54 | ``` 55 | 56 | ### 触发事件 57 | 58 | 59 | ```java 60 | @Controller("/customer") 61 | public class CustomerController { 62 | 63 | @PostAction("/") 64 | public void createCustomer(Customer customer, @Context EventBus eventBus) { 65 | customerDao.save(customer); 66 | eventBus.trigger(new ActEvent(customer)); 67 | } 68 | } 69 | ``` 70 | 71 | ## 两种事件框架的比较 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | 88 | 89 | 90 | 91 | 94 | 97 | 98 | 99 |
ProsCons
简单事件框架 83 | 简单,轻量,更易表达 84 | 86 | 没有类型安全
基于反射的事件方法调用 87 |
类型安全事件框架 92 | 类型安全; 运行时效率更高 93 | 95 | 申明事件响应器以及触发事件的代码较为冗长 96 |
100 | 101 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/di.md: -------------------------------------------------------------------------------- 1 | # 第三章 依赖注入 2 | 3 | ActFramework支持基于[JSR330](https://jcp.org/en/jsr/detail?id=330)的依赖注入. 4 | 5 | ## 1. 申明需要注入的对象 6 | 7 | ActFramework通过`javax.inject.Inject`注解识别需要注入的对象. 你可以通过以下三种标准方式申明需要注入的对象: 8 | 9 | ### 1.1 字段注入 10 | 11 | ```java 12 | public class Foo { 13 | @Inject 14 | private Bar bar; 15 | } 16 | ``` 17 | 18 | **小贴士** 字段注入最为简明,不过对单元测试会造成一些麻烦 19 | 20 | ### 1.2 构造器注入 21 | 22 | ```java 23 | public class Foo { 24 | private Bar bar 25 | 26 | @Inject 27 | public Foo(Bar bar) { 28 | this.bar = bar; 29 | } 30 | } 31 | ``` 32 | 33 | ### 1.3 设置器(Setter)注入 34 | 35 | ```java 36 | public class Foo { 37 | private Bar bar 38 | @Inject 39 | public void setBar(Bar bar) { 40 | this.bar = bar; 41 | } 42 | } 43 | ``` 44 | 45 | ### 1.4 方法参数注入 46 | 47 | 方法参数注入和前面三种注入不一样, 是 ActFramework 特有的功能. Actframework支持三种方法参数注入: 48 | 49 | 1. [请求处理方法](controller.md) 50 | 2. [命令处理方法](cli.md) 51 | 3. [任务方法](job.md) 52 | 53 | 当框架检测到响应函数参数列表中某个参数类型有依赖注入绑定,框架自动使用依赖注入提供该参数值 54 | 55 | ```java 56 | @GetAction("xyz") 57 | public Result handleXyzRequest(String s, int i, ActionContext context, XyzDao dao) { 58 | ... 59 | } 60 | ``` 61 | 62 | 在上面的方法参数中, `ActionContext` 和 `XyzDao` 两个类都是有依赖注入绑定的, 因此 `context` 和 `dao` 两个参数会被依赖注入, 而 `s` 和 `i` 则因为 `String` 和 `int` 类没有依赖注入绑定而从请求参数中解析 63 | 64 | ## 2. 手动获取对象实例 65 | 66 | 应用可以使用 `Act.getInstance` 静态方法来获取对象实例: 67 | 68 | ```java 69 | // this ensure Bar has been injected into Foo 70 | Foo foo = Act.getInstance(Foo.class); 71 | ``` 72 | 73 | ## 3. 通过 Module 类声明绑定 74 | 75 | 如果你以前使用过guice,和通常的Guice应用一样,你可以创建Module类来申明注入绑定规则: 76 | 77 | ```java 78 | public class GreetingModule extends org.osgl.inject.Module { 79 | @Override 80 | protected void configure() { 81 | bind(GreetingService.class).to(GreetingServiceImpl.class); 82 | } 83 | } 84 | ``` 85 | 86 | **小贴士** 在ActFramework中你不必使用Module类来创建`Injector`对象实例。框架会自动寻找所有申明的Module类并在 87 | 内部创建`Injector`实例 88 | 89 | 90 | [返回目录](index.md) 91 | -------------------------------------------------------------------------------- /cn/recipe/job-schedule.md: -------------------------------------------------------------------------------- 1 | # Actframework的任务调度 2 | 3 | ## 任务调度注解 4 | 5 | 在Actframework的应用当中进行任务调度的方式是使用任务调度注解标记任务方法。 6 | ActFramework支持的任务调度注解包括: 7 | 8 | * `@AlongWith` - 指定该方法与某个任务一同执行(异步) 9 | * `@Cron` - 使用类unix的cron表达式来调度执行该方法 10 | * `@Every` - 定期执行该方法 11 | * `@FixedDelay` - 固定间隔执行该方法 12 | * `@InvokeAfter` - 指定该方法在某个任务之后执行(同步) 13 | * `@InvokeBefore` - 指定该方法在某个任务之前执行(同步) 14 | * `@OnAppEvent` - 指定当某个`AppEvent`触发时执行该方法 15 | * `@OnAppStart` - 当App启动时执行该方法 16 | * `@OnAppStop` - 当App停止时执行该方法 17 | 18 | ## 任务方法 19 | 20 | 任务方法的要求: 21 | 22 | 1. 没有返回值,如果有返回值,返回值会被自动忽略 23 | 2. 除了能进行依赖注入的类型,不能有其他类型的参数 24 | 25 | 26 | 任务方法可以是静态的也可以是虚函数。当任务方法不是静态方法的时候,声明方法的类不能是抽象类。任务方法示例: 27 | 28 | * 使用类unix cron表达式调度 29 | 30 | ```java 31 | /** 32 | * This method is scheduled to run every minute 33 | */ 34 | @Cron("0 * * * * ?") 35 | public void backup() { 36 | JobLog.log("SomeService.backup"); 37 | } 38 | ``` 39 | 40 | * 当应用启动完成后调度 41 | 42 | ```java 43 | @OnAppStart(async = true) 44 | public void onAppStartAsync() { 45 | JobLog.log("onAppStartAsync called"); 46 | } 47 | ``` 48 | 49 | * 一个错误声明的任务方法,方法参数列表中有一个无法进行依赖注入的参数 50 | 51 | ```java 52 | @Every("3s") 53 | public String schedule(int n) { 54 | processor.process("DI in field" + n); 55 | return "ignored"; 56 | } 57 | ``` 58 | 59 | * 如果方法声明中的参数可以被依赖注入,则方法是有效的任务方法: 60 | 61 | ```java 62 | /* 63 | * Here we support User.Dao and PostMan are injectable types 64 | */ 65 | @Every("1d") 66 | public void sendPasswordExpirationReminder(User.Dao userDao, PostMan postman) { 67 | Iterable users = userDao.passwordExpireSoon(); 68 | for (User user: users) { 69 | postman.sendPasswordExpireReminderEmail(user); 70 | } 71 | } 72 | ``` 73 | 74 | ## 关于运行环境 75 | 76 | ActFramework是能够进行水平扩容的。假设我们有多台服务器运行同样的ActFramework应用,任务调度势必发生冲突。Act提供了一种巧妙的解决办法。在启动应用的时候可以使用`-Dapp.nodeGroup=xxx`参数来指定当前应用节点的`group`,比如`-Dapp.nodeGroup=job`, 然后在任务方法上使用`Env.Group("job")`来指定这个方法只能在指定为`job`group的应用节点上运行: 77 | 78 | ```java 79 | /** 80 | * This method will get called every x, where 81 | * `x` is configured through `every.check_status` 82 | * configuration 83 | */ 84 | @Every(value = "every.check_status", id = "CHECK_STATUS") 85 | @Env.Group("job") 86 | public void checkStatus() { 87 | JobLog.log("SomeService.checkStatus"); 88 | } 89 | ``` 90 | 91 | 这是一种简单易用的处理多应用服务任务调度冲突的办法 92 | 93 | 需要进一步了解ActFramework的任务调度可以试试运行调试任务调度演示项目: 94 | 95 | * https://github.com/actframework/act-demo-apps/tree/master/jobs 96 | -------------------------------------------------------------------------------- /en/recipe/di-binding.md: -------------------------------------------------------------------------------- 1 | # ActFramework Dependency Injection III - Define Bindings 2 | 3 | We have mentioned one binding defining mechanism in the [ActFramework Dependency Injection II - Inject object type](di-inject-type.md) 4 | 5 | ## 1. Through Module class 6 | 7 | ```java 8 | // Define bindings 9 | public class MyModule extends org.osgl.inject.Module { 10 | protected void configure() { 11 | bind(MyService.class).to(OneService.class); 12 | bind(MyService.class).named("two").to(TwoService.class); 13 | } 14 | } 15 | ``` 16 | 17 | This article will introduce other binding mechanism in ActFramework. 18 | 19 | ## 2. Define Factory class 20 | 21 | Factory is an alternative of Module class. We can rewrite the above Module class using Factory in the following way: 22 | 23 | ```java 24 | public class MyFactory { 25 | 26 | @org.osgl.inject.annotation.Provides 27 | public MyService getOneService(OneService oneService) { 28 | return oneService; 29 | } 30 | 31 | @org.osgl.inject.annotation.Provides 32 | @Named("two") 33 | public MyService getTwoService(TwoService twoService) { 34 | return twoService; 35 | } 36 | 37 | } 38 | ``` 39 | 40 | ## 3. Auto binding 41 | 42 | The thrid way is "Auto binding" which does not require explicitly defining Module or Factory. 43 | However it must add `@act.inject.AutoBind` annotation to the interface or class that needs to be bound to 44 | other implementations 45 | 46 | ```java 47 | // The interface 48 | @act.inject.AutoBind 49 | public interface MyService { 50 | void service(); 51 | } 52 | ``` 53 | 54 | Define default implementation: 55 | 56 | ```java 57 | // The implemention one 58 | public class OneService implements MyService { 59 | public void service() {Act.LOGGER.info("ONE is servicing");} 60 | } 61 | ``` 62 | 63 | Use `@javax.inject.Named` annotation to define Qualified implementation 64 | 65 | ```java 66 | // The implemention two 67 | @javax.inject.Named("two") 68 | public class TwoService implements MyService { 69 | public void service() {Act.LOGGER.info("TWO is servicing");} 70 | } 71 | ``` 72 | 73 | Use the dependency injection 74 | 75 | ```java 76 | // Inject the service 77 | public class Serviced { 78 | 79 | // this one will get bind to the default implementation: OneService 80 | @javax.inject.Inject 81 | private MyService one; 82 | 83 | // this one will get bind to TwoService 84 | @javax.inject.Inject 85 | @javax.inject.Named("two") 86 | private MyService two; 87 | } 88 | ``` 89 | 90 | ## Links 91 | 92 | * [ActFramework Document home](../index) 93 | -------------------------------------------------------------------------------- /en/event.md: -------------------------------------------------------------------------------- 1 | # Event in ActFramework 2 | 3 | ActFramework provides utmost expressive way to bind and dispatch events. 4 | 5 | ## Simple Event Framework 6 | 7 | Simple event framework allows developer to use any String as the event to trigger and bind event handlers. And event handler could be simple as a public method that are annotated with `act.event.On` annotation: 8 | 9 | ### Declaring event handler 10 | 11 | ```java 12 | public class Foo { 13 | @On(value = "customer-created", async = true) 14 | public void sendWelcomeEmail(Contact newCustomer) { 15 | ... 16 | } 17 | } 18 | ``` 19 | 20 | ### Trigger event 21 | 22 | ```java 23 | @Controller("/customer") 24 | public class CustomerController { 25 | 26 | @PostAction("/") 27 | public void createCustomer(Customer customer, @Context EventBus eventBus) { 28 | customerDao.save(customer); 29 | eventBus.trigger("customer-created", customer); 30 | } 31 | } 32 | ``` 33 | 34 | ## Typesafe Event Framework 35 | 36 | Typesafe event framework is more classic way to bind and dispatch event. 37 | 38 | 39 | ### Declaring event handler 40 | 41 | ```java 42 | import act.event.ActEvent; 43 | import act.event.ActEventListenerBase; 44 | import act.util.Async; 45 | 46 | @Async 47 | public class CustomerCreated extends ActEventListenerBase> { 48 | @Override 49 | public void on(ActEvent event) throws Exception { 50 | Customer customer = event.source(); 51 | // send welcome email to customer 52 | } 53 | } 54 | ``` 55 | 56 | ### Trigger event 57 | 58 | 59 | ```java 60 | @Controller("/customer") 61 | public class CustomerController { 62 | 63 | @PostAction("/") 64 | public void createCustomer(Customer customer, @Context EventBus eventBus) { 65 | customerDao.save(customer); 66 | eventBus.trigger(new ActEvent(customer)); 67 | } 68 | } 69 | ``` 70 | 71 | ## Comparison between two event framework 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 85 | 88 | 89 | 90 | 91 | 94 | 97 | 98 | 99 |
ProsCons
Simple event framework 83 | Simple, lightweight and very expressive to declare event handler 84 | 86 | Not type safe
reflection based method call 87 |
Typesafe event framework 92 | Typesafe; better runtime performance 93 | 95 | Slightly verbose to declare event handler 96 |
100 | 101 | [Back to index](index.md) -------------------------------------------------------------------------------- /en/di.md: -------------------------------------------------------------------------------- 1 | # Dependency Injection 2 | 3 | ActFramework support dependency injection based on [JSR330](https://jcp.org/en/jsr/detail?id=330) 4 | 5 | ## Declare inject object 6 | 7 | In ActFramework the `javax.inject.Inject` annotation is used to declare the inject object. 8 | You can declare the inject object follow the three standard ways 9 | 10 | **Field injection** 11 | ```java 12 | public class Foo { 13 | @Inject 14 | private Bar bar; 15 | } 16 | ``` 17 | 18 | **Constructor injection** 19 | ```java 20 | public class Foo { 21 | private Bar bar 22 | 23 | @Inject 24 | public Foo(Bar bar) { 25 | this.bar = bar; 26 | } 27 | } 28 | ``` 29 | 30 | **Setter injection** 31 | ```java 32 | public class Foo { 33 | private Bar bar 34 | @Inject 35 | public void setBar(Bar bar) { 36 | this.bar = bar; 37 | } 38 | } 39 | ``` 40 | 41 | **Tips** The Field injection is clean and simple, but not unit test friendly. 42 | 43 | ## Create object instance in actframework 44 | 45 | ```java 46 | App app = App.instance(); 47 | // this ensure Bar has been injected into Foo 48 | Foo foo = app.newInstance(Foo.class); 49 | ``` 50 | 51 | If you do something like: 52 | 53 | ```java 54 | Foo foo = new Foo(); 55 | ``` 56 | 57 | You don't have the `Bar` injected into your `foo` instance 58 | 59 | ## Inject action method parameter 60 | 61 | Actframework support parameter value injection in the following three cases 62 | 63 | 1. [Controller action handler](controller.md) 64 | 2. [Command handler](cli.md) 65 | 3. [Job method](job.md) 66 | 67 | If framework detect a certain parameter type has provider registered, then it will 68 | inject the parameter value using the provider automatically 69 | 70 | 71 | ```java 72 | // suppose XyzDao has bound provider, then framework will use the provider to 73 | // value for `dao` parameter 74 | public Result handleXyzRequest(String s, int i, ActionContext context, XyzDao dao) { 75 | ... 76 | } 77 | ``` 78 | 79 | ## Declare binding rule with module class 80 | 81 | If you have used `Guice` before, like your usual Guice application, you can create Module 82 | classes to define binding logic, e.g. 83 | 84 | ```java 85 | public class GreetingModule extends org.osgl.inject.Module { 86 | @Override 87 | protected void configure() { 88 | bind(GreetingService.class).to(GreetingServiceImpl.class); 89 | } 90 | } 91 | ``` 92 | 93 | **Tips** Unlike guice, you don't need to create your own `Injector` via calling 94 | `Guice.createInjector(...)`. 95 | Declaring your module classes, and Act will locate them to create the injector for you. 96 | 97 | [Back to index](index.md) 98 | -------------------------------------------------------------------------------- /cn/releases/r1.4.0.md: -------------------------------------------------------------------------------- 1 | # R1.4.0 值得一提的新特性 2 | 3 | 4 | #### #17 WebSocket 支持 5 | 6 | ActFramework 终于支持 WebSocket 了 7 | 8 | 聊天室应用: 9 | 10 | ```java 11 | @WsAction("msg") 12 | public void onMessage(String message, WebSocketContext context) { 13 | context.sendToPeers(message); 14 | } 15 | ``` 16 | 17 | Echo 应用: 18 | 19 | ```java 20 | @WsAction("echo") 21 | public void onMessage(String message, WebSocketContext context) { 22 | context.sendToSelf(message); 23 | } 24 | ``` 25 | 26 | 关于更详细的 websocket 支持, 可参见 http://actframework.org/doc/websocket.md 27 | 28 | 29 | #### #227 支持控制台颜色输出 30 | 31 | 通过配置 `logback.xml` 文件 ([例子在这里](https://gist.github.com/greenlaw110/ac76d69df802dc7da5d2315e0e8df954)) ActFramework 可以输出带颜色的 log 信息: 32 | 33 | ![image](https://user-images.githubusercontent.com/216930/27064441-b4fa45ba-503a-11e7-859a-cb5456c7bb40.png) 34 | 35 | 36 | #### #228 生成 ASCII 字符的 favicon 37 | 38 | 如果 ActFramework 在 `/resources/asset/` 或 `/resources/asset/img` 或 `/resources/asset/image` 目录下找到 `favicon.png` 或 `favicon.ico` 文件,将会在应用启动的时候打印 ASCII 版的 favicon,例如: 39 | 40 | ![image](https://user-images.githubusercontent.com/216930/27064721-27e0fe10-503c-11e7-95fc-2c5004f84672.png) 41 | 42 | 43 | #### #228 将带有 `@Stateless` 注解的类加入到应用的 singleton 注册表中 44 | 45 | 如果一个类上标注有 `@Stateless` 注解,ActFramework 在启动的时候会将该类加入 Singleton 注册表里 46 | 47 | 48 | #### #212 简化 header session mapper 的配置 49 | 50 | 有时候没法依赖 cookie 来保存 session 数据, 这个时候我们需要将 session 数据映射到其他方式,比如 http header 里。在以前的版本中使用 header session mapper 不是特别方便: 51 | 52 | 1. 实现 header session mapper 类 53 | 54 | ```java 55 | package com.mycomp; 56 | 57 | public class MyAppSessionMapper extends act.util.SessionMapper.HeaderSessionMapper { 58 | public SessionMapper() { 59 | super("X-MyApp-"); 60 | } 61 | } 62 | ``` 63 | 64 | 2. 在 app properties 文件中加入: 65 | 66 | ``` 67 | session.mapper=com.mycomp.MyAppSessionMapper 68 | ``` 69 | 70 | R1.4.0 简化了上面的步骤,现在我们只需要在配置文件中加入下面一行即可: 71 | 72 | ``` 73 | session.mapper.header.prefix=X-MyApp- 74 | ``` 75 | 76 | 77 | #### #226 支持定制 banner 文字 78 | 79 | 如果框架在 `/resources/` 找到一个名为 `act_banner.txt` 的文件,将会使用该文件的内容来作为应用启动时的 banner text 输出,例如 80 | 81 | ![image](https://user-images.githubusercontent.com/216930/27065574-6ff589aa-5041-11e7-8806-1c2c76e7d996.png) 82 | 83 | 84 | #### #219 支持 Singleton 的延迟加载 85 | 86 | 现在开发人员可以使用 `@Lazy` 注解和 `@Singleton` 注解一起表示一个 Singleton 类,但是不需要在应用启动过程之加载到 Singleton 注册表中,而是当第一次被使用的时候实例化并加载到 Singleton 注册表, 例如: 87 | 88 | ```java 89 | @Lazy 90 | @Singleton 91 | public class MySingletonClass { 92 | } 93 | ``` 94 | 95 | 点击 https://github.com/actframework/actframework/milestone/7?closed=1 查看 R1.4.0 的 issue 完整清单 96 | -------------------------------------------------------------------------------- /cn/recipe/file-upload.md: -------------------------------------------------------------------------------- 1 | # ActFramework中实现文件上传 2 | 3 | 文件上传是一种常见的web应用功能。这篇小灶讲述如何在ActFramework中实现文件上传,包括单文件上传和多文件上传两种情况。 4 | 5 | 首先我们定义一个Model类`Document`用于演示单文件上传的情况: 6 | 7 | ```java 8 | public class Document implements SimpleBean { 9 | public String desc; 10 | public String subject; 11 | public File attachment; 12 | 13 | public Document(String subject, String desc, File attachment) { 14 | this.desc = desc; 15 | this.subject = subject; 16 | this.attachment = attachment; 17 | } 18 | } 19 | ``` 20 | 21 | 下面是处理单文件上传的请求响应函数: 22 | 23 | ```java 24 | @PostAction("/single") 25 | public Document handleSingleFile(File file, String subject, String desc) { 26 | return new Document(subject, desc, file); 27 | } 28 | ``` 29 | 30 | 对应单文件上传的HTML Form: 31 | 32 | ```html 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 | ``` 48 | 49 | 以上就是Act应用中处理单文件上传的一种方式。下面来看看多文件上传的处理方式。 50 | 51 | 先定义一个Model类用于演示多文件上传: 52 | 53 | ```java 54 | public class Archive implements SimpleBean { 55 | public String desc; 56 | public String subject; 57 | public File[] attachments; 58 | 59 | public Archive(String subject, String desc, File[] attachments) { 60 | this.desc = desc; 61 | this.subject = subject; 62 | this.attachments = attachments; 63 | } 64 | } 65 | ``` 66 | 67 | 处理多文件上传的请求响应函数: 68 | 69 | ```java 70 | @PostAction("/multi") 71 | // Note the param type `File[]` can be changed to `List` 72 | public Archive handleMultipleFiles(File[] files, String subject, String desc) { 73 | return new Archive(subject, desc, files); 74 | } 75 | ``` 76 | 77 | 对应的多文件上传的HTML Form: 78 | 79 | ```html 80 |
81 |
82 | 83 |
84 |
85 | 86 |
87 |
88 | 89 |
90 |
91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 |
99 |
100 | ``` 101 | 102 | 这就是多文件上传的方式。 103 | 104 | 完整的源代码保存在[码云](http://git.oschina.net/greenlaw110/blog_act_file_upload)上 105 | -------------------------------------------------------------------------------- /cn/hibernate.md: -------------------------------------------------------------------------------- 1 | # 使用JPA-Hibernate访问SQL Database 2 | version 1.5.7 3 | ## 安装 4 | 5 | 在你的`pom.xml`文件中加上以下依赖: 6 | 7 | ```xml 8 | 9 | org.actframework 10 | act-hibernate 11 | 1.5.7 12 | 13 | ``` 14 | 15 | 根据你的数据库类型,你也需要加入相应的JDBC访问包的依赖。比如: 16 | 17 | ```xml 18 | 19 | com.h2database 20 | h2 21 | 1.4.178 22 | 23 | ``` 24 | 你还需要引入一个数据库连接池,根据自身条件在以下任选其一: 25 | 26 | ```xml 27 | 28 | com.zaxxer 29 | HikariCP-java7 30 | 2.4.13 31 | 32 | 33 | 34 | com.zaxxer 35 | HikariCP 36 | 2.7.9 37 | 38 | 39 | 40 | com.alibaba 41 | druid 42 | 1.1.16 43 | 44 | ``` 45 | 46 | ## 配置 47 | 48 | ``` 49 | # If you have only one DBPlugin in your class path, then 50 | # you do not need to specify the db.impl configuration 51 | db.impl=act.db.hibernate.HibernatePlugin 52 | # database driver default to org.h2.Driver 53 | db.driver=... 54 | # database Url default to jdbc:h2:mem:tests 55 | db.url=... 56 | # username default is empty 57 | db.username=... 58 | # password default is empty 59 | db.password=... 60 | ``` 61 | 62 | ## 域模型 63 | 64 | 下面创建一个简单的域模型,该模型有三个字段: 65 | 66 | 1. `id` 67 | 1. `password` 68 | 1. `phone` 69 | 1. `email` 70 | 71 | ```java 72 | package com.mycom.myprj; 73 | 74 | import act.util.SimpleBean; 75 | import javax.persistence.*; 76 | 77 | @Entity 78 | @Table(name = "user") 79 | public class User{ 80 | 81 | @Id 82 | @GeneratedValue(strategy = GenerationType.IDENTITY) 83 | private Integer id; 84 | @Column 85 | private String password; 86 | @Column 87 | private String email; 88 | @Column 89 | private String phone; 90 | 91 | public Integer getId() { 92 | return id; 93 | } 94 | 95 | public void setId(Integer id) { 96 | this.id = id; 97 | } 98 | 99 | public String getPassword() { 100 | return password; 101 | } 102 | 103 | public void setPassword(String password) { 104 | this.password = password; 105 | } 106 | 107 | public String getEmail() { 108 | return email; 109 | } 110 | 111 | public void setEmail(String email) { 112 | this.email = email; 113 | } 114 | 115 | public String getPhone() { 116 | return phone; 117 | } 118 | 119 | public void setPhone(String phone) { 120 | this.phone = phone; 121 | } 122 | } 123 | ``` 124 | 125 | ## 数据库访问层 126 | 127 | Dao 只需要很简单的 继承自 act.db.jpa.JPADao 就行了。 128 | 129 | ```java 130 | package com.mycom.myprj; 131 | 132 | import act.db.jpa.JPADao; 133 | import com.mycom.myprj.User; 134 | 135 | public class UserDao extends JPADao { 136 | 137 | } 138 | ``` 139 | -------------------------------------------------------------------------------- /en/recipe/file-upload.md: -------------------------------------------------------------------------------- 1 | # Implement File upload in ActFramework 2 | 3 | File upload is a common web app function. This recipe introduce how to implement file upload in actframework. 4 | Including single and multiple file(s) upload 5 | 6 | First define a model class `Document` to demonstrate single file upload: 7 | 8 | ```java 9 | public class Document implements SimpleBean { 10 | public String desc; 11 | public String subject; 12 | public File attachment; 13 | 14 | public Document(String subject, String desc, File attachment) { 15 | this.desc = desc; 16 | this.subject = subject; 17 | this.attachment = attachment; 18 | } 19 | } 20 | ``` 21 | 22 | Then the request handler method: 23 | 24 | ```java 25 | @PostAction("/single") 26 | public Document handleSingleFile(File file, String subject, String desc) { 27 | return new Document(subject, desc, file); 28 | } 29 | ``` 30 | 31 | The corresponding HTML Form for single file upload: 32 | 33 | ```html 34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 |
48 | ``` 49 | 50 | That's one approach on handling single file upload in Actframework. Next 51 | let's check out how to handle multiple file uploads. 52 | 53 | Again define a model class to demonstrate multiple file uploads: 54 | 55 | ```java 56 | public class Archive implements SimpleBean { 57 | public String desc; 58 | public String subject; 59 | public File[] attachments; 60 | 61 | public Archive(String subject, String desc, File[] attachments) { 62 | this.desc = desc; 63 | this.subject = subject; 64 | this.attachments = attachments; 65 | } 66 | } 67 | ``` 68 | 69 | And the action handler to process multiple file upload request: 70 | 71 | ```java 72 | @PostAction("/multi") 73 | // Note the param type `File[]` can be changed to `List` 74 | public Archive handleMultipleFiles(File[] files, String subject, String desc) { 75 | return new Archive(subject, desc, files); 76 | } 77 | ``` 78 | 79 | The corresponding HTML Form: 80 | 81 | ```html 82 |
83 |
84 | 85 |
86 |
87 | 88 |
89 |
90 | 91 |
92 |
93 | 94 |
95 |
96 | 97 |
98 |
99 | 100 |
101 |
102 | ``` 103 | 104 | 105 | Complele source code used in this recipe can be found on [git@oschina](http://git.oschina.net/greenlaw110/blog_act_file_upload) 106 | -------------------------------------------------------------------------------- /cn/reference/session_flash.md: -------------------------------------------------------------------------------- 1 | # 第五章 控制器, 请求处理方法与响应返回 2 | # 附录 4. Session 与 Flash 的处理详解 3 | 4 | ActFramework 对 Session/Flash 的处理流程如下图所示: 5 | 6 | ![session-flow-chart](https://user-images.githubusercontent.com/216930/38778305-5bf285e2-40fb-11e8-8f65-98a433dd039e.png) 7 | 8 | 1. 处理请求 9 | 1.1 ActFramework 使用 `SessionMapper` 将请求中的某个特定 Cookie 或者 Header 映射为一个字串 10 | 1.2 然后使用 `SessionCodec` 将字串解析为 `H.Session` 或者 `H.Flash` Scope 对象 11 | 2. 处理响应 12 | 2.1 ActFramework 使用 `SessionCodec` 将 `H.Session` 或者 `H.Flash` Scope 对象打包进一个字串 13 | 2.2 然后使用 SessionMapper 将该字串映射到响应特定 Cookie 或者 Header 上 14 | 15 | ### 1 `SessionMapper` 16 | 17 | `SessionMapper` 负责将序列化之后的 session/flash 字串映射到响应上, 以及从请求中获取 session/flash 字串. 具体来说 ActFramework 内置两大类型的 SessionMapper: 18 | 19 | #### 1.1 `CookieSessionMapper` 20 | 21 | `CookieSessionMapper` 将 session 字串写入特定名字的 cookie 之中: 22 | 23 | 1. session cookie 名字为 ${app-short-id}-session; flash cookie 名字为 ${app-short-id}-flash 24 | * 关于 `app-short-id` 的详细内容,参见[启动手册](reference/bootstrap.md#short_id) 25 | 2. cookie path: `/` 26 | 3. cookie domain: 当 localhost 为 host 时, 为空值, 否则为 host 配置 27 | 4. httpOnly: true 28 | 5. secure: `http.secure` 的配置值 29 | 6. value: 序列化之后的 session 或者 flash 字串 30 | 7. ttl: `session.ttl` 配置, 默认为 60 * 30, 即半小时 31 | 32 | #### 1.1 `HeaderSessionMapper` 33 | 34 | `HeaderSessionMapper` 将 session 字串写入某个 HTTP 响应头. Session 头的名字为 `X-Act-Session`, Flash 头的名字为 `X-Act-Flash` 35 | 36 | **注意** 如果配置文件中存在 `jwt=true` 会使用 `Authorization` 作为 Session 头的名字, 且会设置 `session.header.payload.prefix` 为 `Bearer `, 表明 session 字串会存放在 `Authorization` 头的值中, 且使用 `Bearer `作为前缀. 这直接按照标准方式提供了 JWT 服务, 且应用无需任何改变. 37 | 38 | ### 2 `SessionCodec` 39 | 40 | `SessionCodec` 负责将 `H.Session`, `H.Flash` scope 对象和字串做相互转换. 在请求处理过程中, `SessionMapper` 从请求中拿到 Session/Flash 的序列化字串, `SessionCodec` 负责将字串转换为 Scope 对象. 送出响应之前, `SessionCodec` 负责将 Scope 对象序列化为字串, 并交与 `SessionMapper` 放进响应. 41 | 42 | #### 2.1 `DefaultSessionCodec` 43 | 44 | 这是系统默认使用的 `SessionCodec`. 其编码过程如下: 45 | 46 | 1. 如果发现 session/flash 对象没有变化且无内容, 则返回 `null` 47 | 2. 在 session 对象上设定过期时间 (默认为 30*60 - 半小时, 通过 `session.ttl` 配置参数调整) - 仅适用于 session 48 | 3. 对于 session/flash 对象上每一个 (key, value) 配对, 将其用 `\u0001` 拼接, 配对之间使用 `\u0000` 拼接, 生成 payload 字串 49 | 4. 对 payload 字串生成签名, 并使用 `-` 拼接在 payload 字串之前 - 仅适用于 session 50 | 5. 当配置文件中设置了 `session.encrypt` 为 `true` 的情况下, 对 {签名-payload} 做加密处理 - 仅适用于 session 51 | 6. 最后对字串做 url 安全编码生成最终结果 52 | 53 | 当收到请求的时候解码过程是上面的逆操作. 54 | 55 | #### 2.2 `JsonWebTokenSessionCodec` 56 | 57 | 如果配置文件中设置了 `jwt=true` 则使用 `JsonWebTokenSessionCodec` 来处理 Session 对象的编码与解码: 58 | 59 | 1. 如果发现 session 对象没有变化且无内容, 则返回 `null` 60 | 2. 生成一个空的 JWT 对象 61 | 3. 在 JWT 对象上通过 `exp` 设定过期时间 (默认为 30*60 - 半小时, 通过 `session.ttl` 配置参数调整) 62 | 4. 对于 session 对象上每一个 (key, value) 配对, 将其放入 JWT 对象的 payload 中 63 | 5. 将算法名字使用 `alg` JWT 头部. 默认算法为 `SHA256`, 可以通过 `jwt.algo` 配置为其他算法. 目前支持的算法除了 `SHA256`, 还有 `SHA384` 和 `SHA512` 64 | 6. 将 JWT 的头部序列化为 JSON 字串并做 URL 安全编码, payload 部分序列化为 JSON 字串并作 URL 安全编码, 两个部分用 `.` 拼接起来 65 | 7. 对上面生成的字串使用算法进行 hash 计算, 并将计算结果使用 `.` 拼接在最后, 生成 JWT 字串 66 | 67 | 当受到请求的时候发现了 JWT 字串, 采用上面的逆操作生成 session 对象. 68 | -------------------------------------------------------------------------------- /cn/job.md: -------------------------------------------------------------------------------- 1 | # 任务调度 2 | 3 | 任务调度支持是ActFramework的一项很棒的功能,你可以用一种前所未有的简洁方式来进行任务调度,只需要在没有返回类型和参数类型的方法上使用不同的注解制定任务触发条件即可。方法可以是静态的或者虚方法。 4 | 5 | ## 定期运行 6 | 7 | ```java 8 | import act.job.Every; 9 | 10 | public class Foo { 11 | @Every("1d") 12 | public void runEveryOneDay() { 13 | ... 14 | } 15 | 16 | @Every("3h") 17 | public void runEveryThreeHours() { 18 | ... 19 | } 20 | 21 | @Every("5mn") 22 | public void runEveryFiveMinutes() { 23 | ... 24 | } 25 | 26 | @Every("10s") 27 | public void runEveryTenSeconds() { 28 | ... 29 | } 30 | 31 | @Every 32 | public void runEverySecond() { 33 | ... 34 | } 35 | 36 | } 37 | ``` 38 | 39 | ## 固定间隔运行 40 | 41 | ```java 42 | import act.job.FixedDelay; 43 | 44 | public class Foo { 45 | @FixedDelay("40mn") 46 | public void scheduledToRunFourtyMinsAfterLastRunFinished() { 47 | ... 48 | } 49 | } 50 | ``` 51 | 52 | ## Cron调度 53 | 54 | ```java 55 | import act.job.Cron; 56 | 57 | public class Foo { 58 | @Cron("0 0 0/12 * * ?") 59 | public void runEvery12Hours() { 60 | ... 61 | } 62 | 63 | @Cron("cron.password_reminder.scan") 64 | public void runPerConfiguredCrontab() { 65 | ... 66 | } 67 | } 68 | ``` 69 | 70 | ## 和其他任务联动执行 71 | 72 | ```java 73 | package com.mycom.myrpj; 74 | 75 | import act.job.*; 76 | 77 | public class Foo { 78 | @Every("5s") 79 | public void jobA() { 80 | ... 81 | } 82 | 83 | @AlongWith("com.mycom.myrpj.Foo.jobA") 84 | public void asyncInvokeAlongWithJobA() { 85 | ... 86 | } 87 | 88 | @InvokeBefore("com.mycom.myrpj.Foo.jobA") 89 | public void invokeBeforeJobA() { 90 | ... 91 | } 92 | 93 | @InvokeAfter("com.mycom.myrpj.Foo.jobA") 94 | public void invokeAfterJobA() { 95 | ... 96 | } 97 | } 98 | ``` 99 | 100 | ## 处理应用程序事件 101 | 102 | ```java 103 | import act.job.*; 104 | import act.app.event.*; 105 | 106 | public class Foo { 107 | @OnAppStart 108 | public void invokeAfterApplicationStarted() { 109 | ... 110 | } 111 | 112 | @OnAppStart(async = true) 113 | public void asyncInvokeAfterApplicationStarted() { 114 | ... 115 | } 116 | 117 | @OnAppEvent(AppEventId.CONFIG_LOADED) 118 | public void invokeOnAppEvent() { 119 | ... 120 | } 121 | 122 | @OnAppEvent(value = AppEventId.CLASS_LOADED, async = true) 123 | public void asyncInvokeOnAppEvent() { 124 | ... 125 | } 126 | } 127 | ``` 128 | 129 | ## 通过API调度任务 130 | 131 | ```java 132 | @GetAction 133 | public void home(@Context AppJobManager jobManager) { 134 | jobManager.now(new Runnable() { 135 | @Override 136 | public void run() { 137 | System.out.println("home entry invoked"); 138 | } 139 | }); 140 | jobManager.delay(new Runnable() { 141 | @Override 142 | public void run() { 143 | System.out.println("delayed log"); 144 | } 145 | }, "5s"); 146 | String engine = "rythm"; 147 | Controller.Util.render(engine); 148 | } 149 | ``` 150 | 151 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/recipe/user-password-hash.md: -------------------------------------------------------------------------------- 1 | # ActFramework中存储与验证用户密码的机制与应用 2 | 3 | @oschina的[这篇博客](http://www.oschina.net/news/49852/salted-password-hash)详细讲述了保护密码的机制. 作为应用程序开发者理解这些原理是非常重要的, 但是没有理由在每个项目中依据文中所述去实现自己的保护机制, 框架应该在这方面做出足够的支持. 4 | 5 | ActFramework提供简单有效的API来帮助用户处理安全性问题, 其中包括了密码保护与验证. 下面的代码演示如何在应用中使用框架提供的机制: 6 | 7 | ## 代码演示 8 | 9 | ```java 10 | public class User { 11 | private String email; 12 | // 保存password hash而不是明文 13 | private String passhash; 14 | 15 | /** 16 | * 使用`act.Act.crypto().passwordHash(String)`来生成password hash 17 | * @param password the password text 18 | */ 19 | public void setPassword(String password) { 20 | this.passhash = Act.crypto().passwordHash(password); 21 | } 22 | 23 | ... 24 | 25 | public static class Dao extends EbeanDao { 26 | ... 27 | /** 28 | * 验证用户的方法: 使用email搜索用户, 然后对password做匹配 29 | * @param email an email 30 | * @param password a password 31 | * @return a user if the email and password match, else null 32 | */ 33 | public final User authenticate(String email, String password) { 34 | User user = findOneBy("email", email); 35 | if (null == user) { 36 | return null; 37 | } 38 | 39 | return Act.crypto().verifyPassword(password, this.passhash) ? user : null; 40 | } 41 | } 42 | } 43 | ``` 44 | 45 | ## 算法 46 | 47 | ActFramework采用[公认最好](http://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage)的[bcrypt](https://en.wikipedia.org/wiki/Bcrypt)算法处理密码保存与验证 48 | 49 | ## 问题 50 | 51 | ### 1. 盐在哪里? 52 | 53 | [Bcrypt采用随机生成盐并且将盐和hash存放在一起](http://stackoverflow.com/questions/6832445/how-can-bcrypt-have-built-in-salts) 54 | 55 | ### 2. authenticate方法为什么不生成hash然后再从数据库中寻找用户 56 | 57 | 上面的`public final User authenticate(String email, String password)`这样写不是更简单吗: 58 | 59 | ```java 60 | public final User authenticate(String email, String password) { 61 | String hash = Act.crypto().passwordHash(password); 62 | return findOneBy("email, passhash", email.toLowerCase(), hash); 63 | } 64 | ``` 65 | 66 | 答案是不行. 因为Bcrypt每次都随机生成salt和hash值,所以即便用户使用相同的密码,两次调用`Act.crypto().passwordHash(password)`生成的值都是不一样的. 必须用`email`将`User`从数据库里面取出之后再使用`Act.crypto().verifyPassword(String, String)` API来比较 67 | 68 | ### 3. 有没有时间攻击防范 69 | 70 | [JFinal](https://www.oschina.net/p/jfinal?fromerr=R7a8Jq4T)最新版提供了[slowEquals方法](https://www.oschina.net/question/1040143_2218633)用于防范[这篇博客](http://www.oschina.net/news/49852/salted-password-hash)中讲述的时间攻击问题. ActFramework有这方面的防范措施吗? 71 | 72 | 答案是必须的, 在`Act.crypto().verifyPassword(String)`API里面调用Bcrypt的[匹配函数](https://github.com/jeremyh/jBCrypt/blob/master/src/main/java/org/mindrot/BCrypt.java#L774), 用的就是JFinal实现的[slowEquals逻辑](https://github.com/jfinal/jfinal/blob/master/src/main/java/com/jfinal/kit/HashKit.java#L90). 值得一提的是和JFinal的实现相比, Bcrypt做了一点优化, 如果字符串长度不匹配的话, 直接短路返回`false`, 而不会继续slow equals处理. 73 | 74 | ## 链接 75 | 76 | * [文档首页](../index.md) 77 | * [ActFramework官网](http://actframework.org) 78 | * [ActFramework[@开源中国](https://my.oschina.net/u/103410)](https://www.oschina.net/p/actframework) 79 | * [ActFramework[@码云](https://my.oschina.net/buthink)](https://git.oschina.net/actframework/actframework) 80 | -------------------------------------------------------------------------------- /en/job.md: -------------------------------------------------------------------------------- 1 | # Jobs and Scheduler 2 | 3 | Job and scheduler is one of the best feature of ActFramework. It makes job scheduling really a handy piece of work. What you need to do is just create a public method without return result and parameter. The method could be either static or virtual. 4 | 5 | ## Run logic regularly 6 | 7 | ```java 8 | import act.job.Every; 9 | 10 | public class Foo { 11 | @Every("1d") 12 | public void runEveryOneDay() { 13 | ... 14 | } 15 | 16 | @Every("3h") 17 | public void runEveryThreeHours() { 18 | ... 19 | } 20 | 21 | @Every("5mn") 22 | public void runEveryFiveMinutes() { 23 | ... 24 | } 25 | 26 | @Every("10s") 27 | public void runEveryTenSeconds() { 28 | ... 29 | } 30 | 31 | @Every 32 | public void runEverySecond() { 33 | ... 34 | } 35 | 36 | } 37 | ``` 38 | 39 | ## Run logic at fixed delay 40 | 41 | ```java 42 | import act.job.FixedDelay; 43 | 44 | public class Foo { 45 | @FixedDelay("40mn") 46 | public void scheduledToRunFourtyMinsAfterLastRunFinished() { 47 | ... 48 | } 49 | } 50 | ``` 51 | 52 | ## Cron job 53 | 54 | ```java 55 | import act.job.Cron; 56 | 57 | public class Foo { 58 | @Cron("0 0 0/12 * * ?") 59 | public void runEvery12Hours() { 60 | ... 61 | } 62 | 63 | @Cron("cron.password_reminder.scan") 64 | public void runPerConfiguredCrontab() { 65 | ... 66 | } 67 | } 68 | ``` 69 | 70 | ## Invoke along with other job 71 | 72 | ```java 73 | package com.mycom.myrpj; 74 | 75 | import act.job.*; 76 | 77 | public class Foo { 78 | @Every("5s") 79 | public void jobA() { 80 | ... 81 | } 82 | 83 | @AlongWith("com.mycom.myrpj.Foo.jobA") 84 | public void asyncInvokeAlongWithJobA() { 85 | ... 86 | } 87 | 88 | @InvokeBefore("com.mycom.myrpj.Foo.jobA") 89 | public void invokeBeforeJobA() { 90 | ... 91 | } 92 | 93 | @InvokeAfter("com.mycom.myrpj.Foo.jobA") 94 | public void invokeAfterJobA() { 95 | ... 96 | } 97 | } 98 | ``` 99 | 100 | ## Listening to application events 101 | 102 | ```java 103 | import act.job.*; 104 | import act.app.event.*; 105 | 106 | public class Foo { 107 | @OnAppStart 108 | public void invokeAfterApplicationStarted() { 109 | ... 110 | } 111 | 112 | @OnAppStart(async = true) 113 | public void asyncInvokeAfterApplicationStarted() { 114 | ... 115 | } 116 | 117 | @OnAppEvent(AppEventId.CONFIG_LOADED) 118 | public void invokeOnAppEvent() { 119 | ... 120 | } 121 | 122 | @OnAppEvent(value = AppEventId.CLASS_LOADED, async = true) 123 | public void asyncInvokeOnAppEvent() { 124 | ... 125 | } 126 | } 127 | ``` 128 | 129 | ## Adhoc job scheduling 130 | 131 | ```java 132 | @GetAction 133 | public void home(@Provided AppJobManager jobManager) { 134 | jobManager.now(new Runnable() { 135 | @Override 136 | public void run() { 137 | System.out.println("home entry invoked"); 138 | } 139 | }); 140 | jobManager.delay(new Runnable() { 141 | @Override 142 | public void run() { 143 | System.out.println("delayed log"); 144 | } 145 | }, "5s"); 146 | String engine = "rythm"; 147 | Controller.Util.render(engine); 148 | } 149 | ``` 150 | 151 | [Back to index](index.md) 152 | -------------------------------------------------------------------------------- /en/recipe/user-password-hash.md: -------------------------------------------------------------------------------- 1 | # The mechanism of store and persist user password 2 | 3 | 4 | It details the principle and practice to protect user's password in [this article](https://crackstation.net/hashing-security.htm). It is very important for application developer to understand these principle and practices. However it doesn't make sense for every developer to implement the mechanism in every project. Instead framework should provide enough support for that. 5 | 6 | In this recipe we will describe how to use ActFramework's built-in support on secure user's password. 7 | 8 | ## Demo code 9 | 10 | ```java 11 | public class User { 12 | private String email; 13 | // Store password hash instead of plain text 14 | private String passhash; 15 | 16 | /** 17 | * Use ACT's crypto utility to generate password hash 18 | * @param password the password text 19 | */ 20 | public void setPassword(String password) { 21 | this.passhash = Act.crypto().passwordHash(password); 22 | } 23 | 24 | ... 25 | 26 | public static class Dao extends EbeanDao { 27 | ... 28 | /** 29 | * Very password: find out user with emai, and then verify the password 30 | * against the stored password hash 31 | * @param email an email 32 | * @param password a password 33 | * @return a user if the email and password match, else null 34 | */ 35 | public final User authenticate(String email, String password) { 36 | User user = findOneBy("email", email); 37 | if (null == user) { 38 | return null; 39 | } 40 | 41 | return Act.crypto().verifyPassword(password, this.passhash) ? user : null; 42 | } 43 | } 44 | } 45 | ``` 46 | 47 | ## Algorithm 48 | 49 | ActFramework utilize the [well known](http://security.stackexchange.com/questions/4781/do-any-security-experts-recommend-bcrypt-for-password-storage) [bcrypt](https://en.wikipedia.org/wiki/Bcrypt) to calculate/verify password hash. 50 | 51 | ## Questions 52 | 53 | ### 1. Where is my salt? 54 | 55 | [Bcrypt generate salt randomly and store the salt with calculated hash together](http://stackoverflow.com/questions/6832445/how-can-bcrypt-have-built-in-salts) 56 | 57 | ### 2. Can I use generated hash value to look up user in database? 58 | 59 | Isn't much simpler if we update the `authenticate` method show above with the following implementation? 60 | 61 | ```java 62 | public final User authenticate(String email, String password) { 63 | String hash = Act.crypto().passwordHash(password); 64 | return findOneBy("email, passhash", email.toLowerCase(), hash); 65 | } 66 | ``` 67 | 68 | The answer is no, because Bcrypt generate random salt of different hash every time even if you pass the same password. It has to use `email` to fetch the user and verify the password and hash explicitly using `Act.crypto().verifyPassword(String, String)` API. 69 | 70 | ### 3. Is there any prevention for [remot timing attack](https://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf) 71 | 72 | 73 | Yes, Bcrypt's [verification function](https://github.com/jeremyh/jBCrypt/blob/master/src/main/java/org/mindrot/BCrypt.java#L774) applied slow equal logic to prevent remote timing attack. 74 | 75 | ## Links 76 | 77 | * [Document home](../index.md) 78 | * [ActFramework](http://actframework.org) 79 | * [ActFramework@oschina](https://my.oschina.net/u/103410)](https://www.oschina.net/p/actframework) 80 | * [ActFramework@github](https://github.com/actframework/actframework) 81 | -------------------------------------------------------------------------------- /en/releases/r1.4.0.md: -------------------------------------------------------------------------------- 1 | # What's New and Noteworthy in R1.4.0 2 | 3 | 4 | #### #17 Support WebSocket 5 | 6 | WebSocket is now supported in ActFramework! 7 | 8 | A chat room app: 9 | 10 | ```java 11 | @WsAction("msg") 12 | public void onMessage(String message, WebSocketContext context) { 13 | context.sendToPeers(message); 14 | } 15 | ``` 16 | 17 | An echo server: 18 | 19 | ```java 20 | @WsAction("echo") 21 | public void onMessage(String message, WebSocketContext context) { 22 | context.sendToSelf(message); 23 | } 24 | ``` 25 | 26 | For more about websocket support, please refer to http://actframework.org/doc/websocket.md 27 | 28 | 29 | #### #227 Support colorful console output 30 | 31 | Now with propery logback configuration (example at [here](https://gist.github.com/greenlaw110/ac76d69df802dc7da5d2315e0e8df954)) ActFramework output colorful log message: 32 | 33 | ![image](https://user-images.githubusercontent.com/216930/27064441-b4fa45ba-503a-11e7-859a-cb5456c7bb40.png) 34 | 35 | 36 | #### #228 Generate ASCII banner for favicon 37 | 38 | If ActFramework found `favicon.png` or `favicon.ico` file under `/resources/asset/` or `/resources/asset/img` or `/resources/asset/image` folder it will print out ASCII art of the favicon when app started, e.g. 39 | 40 | ![image](https://user-images.githubusercontent.com/216930/27064721-27e0fe10-503c-11e7-95fc-2c5004f84672.png) 41 | 42 | 43 | #### #228 Automatically register a class with `@Stateless` tag into app's singleton registry 44 | 45 | `@Stateless` annotated class now will registered to app's singleton registry when app started 46 | 47 | 48 | #### #212 Support easy configuring of header session mapper 49 | 50 | Sometimes relies on cookie to store app session data is not viable, in which case ActFramework provides `SessionMapper` feature to allow app developer to customize session data serialization/deserialization logic. 51 | 52 | A common session mapper implementation is to map session data to HTTP header. Here is how to do it in previous ActFramework versions: 53 | 54 | 1. implement app's header session mapper: 55 | 56 | ```java 57 | package com.mycomp; 58 | 59 | public class MyAppSessionMapper extends act.util.SessionMapper.HeaderSessionMapper { 60 | public SessionMapper() { 61 | super("X-MyApp-"); 62 | } 63 | } 64 | ``` 65 | 66 | 2. update app properties file by adding the following line: 67 | 68 | ``` 69 | session.mapper=com.mycomp.MyAppSessionMapper 70 | ``` 71 | 72 | Now with R1.4.0, it simplified the usage of header session mapper by just adding the following app configuration item: 73 | 74 | ``` 75 | session.mapper.header.prefix=X-MyApp- 76 | ``` 77 | 78 | 79 | #### #226 Support customized banner text 80 | 81 | When framework found a file named `act_banner.txt` in `/resources/` directory, it will print out the content of that file instead of the default banner text. For example: 82 | 83 | ![image](https://user-images.githubusercontent.com/216930/27065574-6ff589aa-5041-11e7-8806-1c2c76e7d996.png) 84 | 85 | 86 | #### #219 Support Lazy initialized singleton 87 | 88 | Now developer can use `@Lazy` annotation together with `@Singleton` annotation, in which case the framework will not aggresively load the instance of the type into singleton registry on app started. Instead it will load the instance on demand: 89 | 90 | ```java 91 | @Lazy 92 | @Singleton 93 | public class MySingletonClass { 94 | } 95 | ``` 96 | 97 | Check https://github.com/actframework/actframework/milestone/7?closed=1 for full list of issues closed in R1.4.0 98 | -------------------------------------------------------------------------------- /cn/cli.md: -------------------------------------------------------------------------------- 1 | # 命令行应用 2 | 3 | 命令行是一种历史悠久且极有生命力的人机交互界面。即便在多数情况下最新的Web应用提供了各种丰富易用的人机交互体验,命令行应用依然是及其重要的工具,因为它: 4 | 5 | 1. 适合系统管理员使用 6 | 1. 通常只用于内部网中,所以安全性更高 7 | 8 | ActFramework考虑了命令行应用的开发需求,特地提供了一些工具让开发命令行应用简单地不可思议。 9 | 10 | ## 创建命令响应器 11 | 12 | ```java 13 | import act.app.CliContext; 14 | import act.cli.Command; 15 | import act.cli.Optional; 16 | import act.cli.Required; 17 | import act.cli.JsonView; 18 | import act.util.PropSpec; 19 | 20 | public class CustomerAdmin { 21 | 22 | @Inject 23 | private Customer.Dao customerDao; 24 | 25 | @Command(name = "cust.list", help = "list customers") 26 | @PropSpec("email,fullName as name,phone") 27 | public Iterable list( 28 | @Optional(lead = "-q", help = "optionally specify the query string") String q 29 | ) { 30 | return customerDao.search(q); 31 | } 32 | 33 | @Command(name = "cust.show", help = "show customer details") 34 | @JsonView 35 | public Customer show( 36 | @Required(lead = "--id", help = "specify the customer ID") String id 37 | ) { 38 | return customerDao.findById(id); 39 | } 40 | } 41 | ``` 42 | 43 | ## 运行命令行并执行命令 44 | 45 | ```bash 46 | #nc是一种简单的网络链接工具,如果系统中没有nc,可以使用telnet作为替代 47 | $nc localhost 5461 48 | act[1sbKUt2E1]> 49 | 50 | act[1sbKUt2E1]>help -a 51 | 52 | APPLICATION COMMANDS 53 | cust.list - list customers 54 | cust.show - show customer details 55 | 56 | act[1sbKUt2E1]>cust.list -h 57 | Usage: cust.list [options] 58 | list customers 59 | 60 | Options: 61 | -q optionally specify the query string 62 | 63 | act[1sbKUt2E1]>cust.list -q "com1.com" 64 | +--------------------------+--------------------+--------------+------------+ 65 | | ID | EMAIL | NAME | PHONE | 66 | +--------------------------+--------------------+--------------+------------+ 67 | | 5684c35e6e250d52baa94935 | john@com1.com | John Smith | 11,111,111 | 68 | | 569174e5b47e271add049154 | peter@com1.com | Peter Brad | 22,222,222 | 69 | +--------------------------+--------------------+--------------+------------+ 70 | Items found: 2 71 | 72 | act[1sbKUt2E1]>cust.show -h 73 | Usage: cust.show [options] 74 | show customer details 75 | 76 | Options: 77 | --id specify the customer ID 78 | 79 | act[1sbKUt2E1]>cust.show --id 5684c35e6e250d52baa94935 80 | { 81 | "id": "5684c35e6e250d52baa94935", 82 | "email": "john@com1.com", 83 | "firstName": "John", 84 | "lastName": "Smith", 85 | "phone": "11,111,111", 86 | ... 87 | } 88 | ``` 89 | 90 | ## 在RESTful控制器和CLI命令器上的代码复用 91 | 92 | 通常你会发现同样的逻辑总是出现在控制器和命令响应器上。这意味着你可能需要一些拷贝粘贴工作,作为一名负责任的猿,你会对此非常恼火。所幸ActFramework允许你将同样的代码同时用于控制器和命令响应器,只需使用相应的注解即可: 93 | 94 | ```java 95 | @Controller("/customer") 96 | public class CustomerService { 97 | 98 | @Inject 99 | private Customer.Dao customerDao; 100 | 101 | @GetAction("/") 102 | @Command(name = "cust.list", help = "list customers") 103 | @PropSpec("email,fullName as name,phone") 104 | public Iterable list( 105 | @Optional(lead = "-q", help = "optionally specify the query string") String q 106 | ) { 107 | return customerDao.search(q); 108 | } 109 | 110 | @GetAction("/{id}") 111 | @Command(name = "cust.show", help = "show customer details") 112 | @JsonView 113 | public Customer show( 114 | @Required(lead = "--id", help = "specify the customer ID") String id 115 | ) { 116 | return customerDao.findById(id); 117 | } 118 | } 119 | ``` 120 | 121 | [回目录](index.md) -------------------------------------------------------------------------------- /cn/recipe/singleton.md: -------------------------------------------------------------------------------- 1 | # 如何在ActFramework中创建单例 2 | 3 | Java中创建单例是一个[有趣的话题](http://www.javaworld.com/article/2073352/core-java/simply-singleton.html) 人们对此进行了[很多讨论](http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java). 4 | 5 | ## 常规方法 6 | 7 | 自从有了Java 5之后创建单例就变得及其简单了: 8 | 9 | ```java 10 | public enum MySingleton { 11 | INSTANCE; 12 | // your class implementation starts here 13 | public String foo() { 14 | return "foo" 15 | } 16 | } 17 | ``` 18 | 19 | 引用单例的方法: 20 | 21 | ```java 22 | String s = MySingleton.INSTANCE.foo(); 23 | ``` 24 | 25 | 如果你的单例需要继承其他类,你就不能使用`enum`方式。这个时候你可以使用老方法: 26 | 27 | ```java 28 | public class MySingleton extends MyBaseClass { 29 | public static final MySingleton INSTANCE = new MySingleton(); 30 | } 31 | ``` 32 | 33 | 如果希望INSTANCE私有的话: 34 | 35 | ```java 36 | public class MySingleton extends MyBaseClass { 37 | private static final MySingleton INSTANCE = new MySingleton(); 38 | public static MySingleton instance() { 39 | return INSTANCE; 40 | } 41 | public String foo() { 42 | return "foo" 43 | } 44 | } 45 | ``` 46 | 47 | 或者说你希望延迟加载的话: 48 | 49 | ```java 50 | public class MySingleton extends MyBaseClass { 51 | private static volatile final MySingleton INSTANCE; 52 | public static MySingleton instance() { 53 | if (null == INSTANCE) { 54 | synchronized(MySingleton.class) { 55 | if (null == INSTANCE) { 56 | INSTANCE = new MySingleton(); 57 | } 58 | } 59 | } 60 | return INSTANCE; 61 | } 62 | } 63 | ``` 64 | 65 | **提示** 你需要在`INSTANCE`申明上引入`volatile`关键字,否则就会落入[双锁检查陷阱](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) 66 | 67 | ## ActFramework特别支持 68 | 69 | 在ActFramework你还有一种简单的使用单例模式的办法,在类申明上加入`javax.inject.Singleton`注解: 70 | 71 | ```java 72 | @javax.inject.Singleton 73 | public class MySingleton extends MyBaseClass { 74 | public String foo() { 75 | return "foo" 76 | } 77 | } 78 | ``` 79 | 80 | 使用单例对象: 81 | 82 | ```java 83 | String s = act.app.App.instance().singleton(MySingleton.class).foo(); 84 | ``` 85 | 86 | 如果你的类不需要继承其他类的话你也可以考虑直接继承`act.util.SingletonBase`: 87 | 88 | ```java 89 | public class MySingleton extends act.util.SingletonBase { 90 | public String foo() { 91 | return "foo" 92 | } 93 | } 94 | ``` 95 | 96 | 这个方法和`javax.inject.Singleton`注解效果是一样的,而且引用单例对象变得更加简单: 97 | 98 | ```java 99 | MySingleton singleton = MySingleton.instance(); 100 | String s = singleton.foo(); 101 | 102 | // you can also use the App.singleton(Class) way to fetch your singleton: 103 | MySingleton singleton2 = act.app.App.instance().singleton(MySingleton.class); 104 | 105 | // or you can just ask App to give you an new instance: 106 | MySingleton singleton3 = act.app.App.instance().getInstance(MySingleton.class); 107 | ``` 108 | 109 | 上面的代码中,ActFramework确保`singleton`, `singleton2` 和 `singleton3` 都指向同一个实例。你还可以把单例注入到其他类中: 110 | 111 | ```java 112 | public class MySingletonConsumer { 113 | @Inject 114 | private MySingleton mySingleton; 115 | } 116 | ``` 117 | 118 | 或者注入到你的[响应器](../controller.md#term)方法中: 119 | 120 | ```java 121 | @GetAction("/foo") 122 | public String sayFoo(MySingleton mySingleton) { 123 | return mySingleton.foo(); 124 | } 125 | ``` 126 | 127 | ## 总结 128 | 129 | * 创建单例最简明的方法是把你的类申明为`enum` 130 | * ActFramework识别`javax.inject.Singleton`注解并自动加载单例实例到内存。应用程序可以使用`act.app.App.instance().singleton(Class)`调用来获取单例 131 | * ActFramework实现了单例注入 132 | * ActFramework提供`SingletonBase`类。如果应用类继承了`SingletonBase`,单例可以通过`MySingleton.instance()`调用来获取 133 | 134 | -------------------------------------------------------------------------------- /en/recipe/job-schedule.md: -------------------------------------------------------------------------------- 1 | # Actframework: Job scheduling 2 | 3 | ## Annotations 4 | 5 | ActFramework scans annotations on method to schedule the job execution. Belows are support job schedule annotions: 6 | 7 | * `@AlongWith` - Specify this method shall be invoked along with another job asynchronously 8 | * `@Cron` - Schedule a method invocation with unix-like cron expression 9 | * `@Every` - Schedule a method to be invoked in a fixed rate 10 | * `@FixedDelay` - Schedule a method to be invoked at fixed delay 11 | * `@InvokeAfter` - Schedule a method to be invoked after another job (synchrously) 12 | * `@InvokeBefore` - Schedule a method to be invoked before another job (synchrously) 13 | * `@OnAppEvent` - Specify the method to be invoked on a certain `AppEvent` 14 | * `@OnAppStart` - Invoke the method on App start 15 | * `@OnAppStop` - Invoke the method on App stop 16 | 17 | ## Job method 18 | 19 | Requirements to a Job method: 20 | 21 | 1. No return value. Otherwise the returned value will be ignored 22 | 2. Cannot have parameters except the the ones can be injected 23 | 24 | 25 | Job method could be either static or virtual. If the Job method is virtual, the declaring class must not be abstract. 26 | 27 | Job method examples: 28 | 29 | * unix-like cron scheduling 30 | 31 | ```java 32 | /** 33 | * This method is scheduled to run every minute 34 | */ 35 | @Cron("0 * * * * ?") 36 | public void backup() { 37 | JobLog.log("SomeService.backup"); 38 | } 39 | ``` 40 | 41 | * Run on application start 42 | 43 | ```java 44 | @OnAppStart(async = true) 45 | public void onAppStartAsync() { 46 | JobLog.log("onAppStartAsync called"); 47 | } 48 | ``` 49 | 50 | * A fault Job method declaration: the method list contains parameter that cannot be injected 51 | 52 | ```java 53 | @Every("3s") 54 | public String schedule(int n) { 55 | processor.process("DI in field" + n); 56 | return "ignored"; 57 | } 58 | ``` 59 | 60 | * However if the parameters can be injected, then it should okay 61 | 62 | ```java 63 | /* 64 | * Here we support User.Dao and PostMan are injectable types 65 | */ 66 | @Every("1d") 67 | public void sendPasswordExpirationReminder(User.Dao userDao, PostMan postman) { 68 | Iterable users = userDao.passwordExpireSoon(); 69 | for (User user: users) { 70 | postman.sendPasswordExpireReminderEmail(user); 71 | } 72 | } 73 | ``` 74 | 75 | ## About runtime environment 76 | 77 | ActFramework is designed to support scale horizontally. However if we scale our app to multiple nodes, 78 | the job scheduling will clash to each other as all node will run the Job scheduling. 79 | 80 | In order to address this issue, Actframework provided a way to allow it specify the node's group. For 81 | example, if you want job to be run only on a certain node, start the act application using 82 | `-Dapp.nodeGroup=job` to mark the node as `job` group. Then use `Env.Group("job")`annotation on 83 | the method to be scheduled so that the method will be invoked only on app that runs in `job` group 84 | 85 | ```java 86 | /** 87 | * This method will get called every x, where 88 | * `x` is configured through `every.check_status` 89 | * configuration 90 | */ 91 | @Every(value = "every.check_status", id = "CHECK_STATUS") 92 | @Env.Group("job") 93 | public void checkStatus() { 94 | JobLog.log("SomeService.checkStatus"); 95 | } 96 | ``` 97 | 98 | If you want to understand more about job scheduling of Actframework, have a play with the job demo app: 99 | 100 | * https://github.com/actframework/act-demo-apps/tree/master/jobs 101 | -------------------------------------------------------------------------------- /en/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | ActFramework support building routing table in three different ways: 4 | 5 | 1. Through annotation put on an action handler method 6 | 1. Through `resources/routes.conf` file 7 | 1. Through configuration API 8 | 9 | ## Routing with actions handler method annotation 10 | 11 | The following annotation are supported: 12 | 13 | 1. `org.osgl.mvc.annotation.Action` 14 | 1. `org.osgl.mvc.annotation.GetAction` 15 | 1. `org.osgl.mvc.annotation.PostAction` 16 | 1. `org.osgl.mvc.annotation.PutAction` 17 | 1. `org.osgl.mvc.annotation.DeleteAction` 18 | 19 | Sample code: 20 | 21 | ```java 22 | @GetAction("/profile/{id}") 23 | public Profile getProfile(String id) { 24 | return dao.findById(id); 25 | } 26 | 27 | @PostAction("/profile") 28 | public void createProfile (Profile profile) { 29 | dao.save(profile); 30 | } 31 | 32 | @PutAction("/profile/{id}/address") 33 | public void updateAddress(String id, Address address) { 34 | Profile profile = dao.findById(id); 35 | notFoundIfNull(profile); 36 | profile.setAddress(address); 37 | profile.update(profile); 38 | } 39 | 40 | @DeleteAction("/profile/{id}") 41 | public void deleteProfile(String id) { 42 | dao.deleteById(id); 43 | } 44 | ``` 45 | 46 | **Tips**: Use `Action` annotation when an action handler needs to handler request through multiple HTTP methods: 47 | 48 | ```java 49 | @Action("/", methods = {H.Method.GET, H.Method.POST}) 50 | public void home() {} 51 | ``` 52 | 53 | **Tips**: You can have one action handler to answer multiple request URL: 54 | 55 | ```java 56 | @GetAction({"/profile/{id}", "/profile"}) 57 | public Profile getProfile(String id) { 58 | return dao.findById(id); 59 | } 60 | ``` 61 | 62 | The above code allows the front end send request to `getProfile` using two different styles: 63 | 64 | 1. `/profile/` 65 | 2. `/profile?id=` 66 | 67 | ## The `routes` file 68 | 69 | If you prefer the `PlayFramework` style routing, you are free to create a `routes` file under your `/src/main/resources` folder. The equivalent `routes` file replacing the above annotations should be look like (suppose the controller class is `com.mycom.myprj.MyController`): 70 | 71 | ``` 72 | GET /profile/{id} com.mycom.myprj.MyController.getProfile 73 | POST /profile com.mycom.myprj.MyController.createProfile 74 | PUT /profile/{id}/address com.mycom.myprj.MyController.updateAddress 75 | DELETE /profile/{id} 76 | ``` 77 | 78 | In general any route entry defined in the `routes` file composed of three parts: 79 | 80 | ``` 81 | (GET|POST|DELETE|PUT|*) 82 | ``` 83 | 84 | ### Handler directives 85 | 86 | By default the `handler` part of route entry indicate an action handler method defined in a controller class. However you can use handler directive to specify handler that are not action handler method. E.g. 87 | 88 | ``` 89 | # map /tmp url to /tmp dir 90 | GET /tmp externalfile:/tmp 91 | GET /public file:/public 92 | GET /3215430325 echo:some-code 93 | GET /google redirect:http://google.com 94 | ``` 95 | 96 | Act has four built-in directives: 97 | 98 | 1. `echo`: Any string after `echo:` will be sent to the response. This is especially useful when service, e.g. Godaddy needs you to respond with a specific code when request is sent to a certain endpoint 99 | 1. `file`: Allows you to send back static file under the application's base dir 100 | 1. `externalfile`: Allows you to send back any static file 101 | 1. `redirect`: Allows you to specify a redirection 102 | 103 | **Notes**, the entry specified in `routes` file can overwrite the route specified with action annotations. 104 | 105 | ## Creating route with configuration API 106 | 107 | TBD 108 | 109 | [Back to index](index.md) -------------------------------------------------------------------------------- /cn/email.md: -------------------------------------------------------------------------------- 1 | # 发送邮件 2 | 3 | 在ActFramework发送邮件是非常简单的工作。如果你的某个类具有邮件发送方法,你需要: 4 | 5 | 1. 使用`act.mail.Mailer`注解标记该类 6 | 1. 让该类继承`Mailer.Util`类或者使用静态引入 7 | 8 | `import static act.mail.Mailer.Util.*` 9 | 10 | ## 创建邮件发送方法 11 | 12 | ```java 13 | package com.mycom.myprj; 14 | 15 | import act.conf.AppConfig; 16 | import act.job.Every; 17 | import act.mail.Mailer; 18 | import org.osgl.logging.*; 19 | import org.osgl.util.E; 20 | import org.osgl.util.S; 21 | 22 | import javax.inject.Inject; 23 | 24 | /** 25 | * This class demonstrate how to write an email sender 26 | * in act framework. 27 | */ 28 | @Mailer 29 | public class PostOffice extends Mailer.Util { 30 | 31 | public void sendWelcome(Contact who) { 32 | to(who.getEmail()); 33 | subject("Welcome letter"); 34 | send(who); 35 | } 36 | 37 | public void sendBye(Contact who) { 38 | to(who.getEmail()); 39 | send(who); 40 | } 41 | 42 | } 43 | ``` 44 | 45 | ## 创建邮件模板 46 | 47 | 为每一个邮件发送方法创建对应的模板。模板的位置和控制器响应方法模板位置依照同样规则,对于上例中的`sendWelcome`和`sendBye`方法,对应的模板文件分别为: 48 | 49 | 1. `src/main/resources/rythm/com/mycom/myprj/PostOffice/sendWelcome.html`: 50 | 51 | ```html 52 | 53 | 54 | 55 | @args com.mycom.myprj.Contact who 56 |

Welcome @who.getFirstName()!

57 |

Blah Blah

58 | 59 | 60 | ``` 61 | 62 | 1. `src/main/resources/rythm/com/mycom/myprj/PostOffice/sendBye.html`: 63 | 64 | ```html 65 | 66 | 67 | 68 | @args com.mycom.myprj.Contact who 69 |

Good bye @who.getFirstName()!

70 | 71 | 72 | ``` 73 | 74 | ## 调用邮件发送方法 75 | 76 | 你可以简单地通过方法调用发送邮件 77 | 78 | ```java 79 | public class MyController { 80 | 81 | @Inject 82 | private PostOffice postOffice; 83 | 84 | @PostAction("/contact") 85 | public void createContact(Contact contact) { 86 | contactDao.save(contact); 87 | postOffice.sendWelcome(contact); 88 | } 89 | 90 | @PutAction("/contact/{contactId}/signOff") 91 | public void signOff(String contactId) { 92 | Contact contact = contactDao.findById(contactId); 93 | contact.signOff(); 94 | contactDao.save(contact); 95 | postOffice.sendBye(contact); 96 | } 97 | } 98 | ``` 99 | 100 | ## 异步调用邮件发送方法 101 | 102 | 因为涉及远程通信,邮件发送通常来讲是比较耗时的操作。如果在控制器中调用邮件发送会造成结果返回延时。通常的做法是采用异步方式发送邮件,在ActFramework中,你可以通过事件分派来实现: 103 | 104 | 1. 使用`act.event.On`注解将邮件发送方法标注为异步事件响应器 105 | 106 | ```java 107 | @On(value = "contact-created", async = true) 108 | public void sendWelcome(Contact who) { 109 | to(who.getEmail()); 110 | subject("Welcome letter"); 111 | send(who); 112 | } 113 | 114 | @On(value = "contact-signed-off", async = true) 115 | public void sendBye(Contact who) { 116 | to(who.getEmail()); 117 | send(who); 118 | } 119 | ``` 120 | 121 | 1. 在需要调用邮件发送方法的时候触发事件 122 | 123 | ```java 124 | public class MyController { 125 | 126 | @Inject 127 | EventBus eventBus; 128 | 129 | @PostAction("/contact") 130 | public void createContact(Contact contact) { 131 | contactDao.save(contact); 132 | eventBus.trigger("contact-created", contact); 133 | } 134 | 135 | @PutAction("/contact/{contactId}/signOff") 136 | public void signOff(String contactId) { 137 | Contact contact = contactDao.findById(contactId); 138 | contact.signOff(); 139 | contactDao.save(contact); 140 | eventBus.trigger("contact-signed-off", contact); 141 | } 142 | } 143 | ``` 144 | 145 | [返回目录](index.md) -------------------------------------------------------------------------------- /en/cli.md: -------------------------------------------------------------------------------- 1 | # Command Line Interface 2 | 3 | CLI is a very old but yet long live interface between people and computer. Even for the latest web application that provides different kinds of interfaces, CLI is still an important thing which 4 | 5 | 1. is administrator friendly 6 | 1. more secure because it could be restricted to internal network 7 | 8 | ActFramework takes CLI into consideration and makes creating CLI an unexpected simple task for application developers. 9 | 10 | ## Create commander 11 | 12 | ```java 13 | import act.app.CliContext; 14 | import act.cli.Command; 15 | import act.cli.Optional; 16 | import act.cli.Required; 17 | import act.cli.JsonView; 18 | import act.util.PropSpec; 19 | 20 | public class CustomerAdmin { 21 | 22 | @Inject 23 | private Customer.Dao customerDao; 24 | 25 | @Command(name = "cust.list", help = "list customers") 26 | @PropSpec("email,fullName as name,phone") 27 | public Iterable list( 28 | @Optional(lead = "-q", help = "optionally specify the query string") String q 29 | ) { 30 | return customerDao.search(q); 31 | } 32 | 33 | @Command(name = "cust.show", help = "show customer details") 34 | @JsonView 35 | public Customer show( 36 | @Required(lead = "--id", help = "specify the customer ID") String id 37 | ) { 38 | return customerDao.findById(id); 39 | } 40 | } 41 | ``` 42 | 43 | ## Run CLI and issue commander 44 | 45 | ```bash 46 | $nc localhost 5461 47 | act[1sbKUt2E1]> 48 | 49 | act[1sbKUt2E1]>help -a 50 | 51 | APPLICATION COMMANDS 52 | cust.list - list customers 53 | cust.show - show customer details 54 | 55 | act[1sbKUt2E1]>cust.list -h 56 | Usage: cust.list [options] 57 | list customers 58 | 59 | Options: 60 | -q optionally specify the query string 61 | 62 | act[1sbKUt2E1]>cust.list -q "com1.com" 63 | +--------------------------+--------------------+--------------+------------+ 64 | | ID | EMAIL | NAME | PHONE | 65 | +--------------------------+--------------------+--------------+------------+ 66 | | 5684c35e6e250d52baa94935 | john@com1.com | John Smith | 11,111,111 | 67 | | 569174e5b47e271add049154 | peter@com1.com | Peter Brad | 22,222,222 | 68 | +--------------------------+--------------------+--------------+------------+ 69 | Items found: 2 70 | 71 | act[1sbKUt2E1]>cust.show -h 72 | Usage: cust.show [options] 73 | show customer details 74 | 75 | Options: 76 | --id specify the customer ID 77 | 78 | act[1sbKUt2E1]>cust.show --id 5684c35e6e250d52baa94935 79 | { 80 | "id": "5684c35e6e250d52baa94935", 81 | "email": "john@com1.com", 82 | "firstName": "John", 83 | "lastName": "Smith", 84 | "phone": "11,111,111", 85 | ... 86 | } 87 | ``` 88 | 89 | ## Reuse code between RESTful controller and CLI commander 90 | 91 | For many time you might find that you are repeating code between RESTful controller and CLI commander. Don't be annoying, ActFramework allows to multiplex your code in both controller and commander, just add the relevant annotation and your job is done! 92 | 93 | ```java 94 | @Controller("/customer") 95 | public class CustomerService { 96 | 97 | @Inject 98 | private Customer.Dao customerDao; 99 | 100 | @GetAction("/") 101 | @Command(name = "cust.list", help = "list customers") 102 | @PropSpec("email,fullName as name,phone") 103 | public Iterable list( 104 | @Optional(lead = "-q", help = "optionally specify the query string") String q 105 | ) { 106 | return customerDao.search(q); 107 | } 108 | 109 | @GetAction("/{id}") 110 | @Command(name = "cust.show", help = "show customer details") 111 | @JsonView 112 | public Customer show( 113 | @Required(lead = "--id", help = "specify the customer ID") String id 114 | ) { 115 | return customerDao.findById(id); 116 | } 117 | } 118 | ``` 119 | 120 | [Back to index](index.md) -------------------------------------------------------------------------------- /cn/recipe/json-response.md: -------------------------------------------------------------------------------- 1 | # 如何在Act中控制JSON返回对象的字段 2 | 3 | 本文讲述如何在Act应用中控制返回JSON对象的字段。不同的场景返回同样对象的时候可能会要求返回不同的对象字段。 4 | 最简单的例子是基本上所有返回`User`对象都要求去掉password字段。在这篇文章中我们讨论如何在Act应用中实现对JSON返回的控制。 5 | 6 | 首先创建一个`Article`类,以及对该资源的RESTful服务接口: 7 | 8 | ```java 9 | @Entity("article") 10 | public class Article extends MorphiaAdaptiveRecord
{ 11 | 12 | @Controller("article") 13 | public static class Service extends MorphiaDao
{ 14 | 15 | @GetAction 16 | public Iterable
list() { 17 | return findAll(); 18 | } 19 | 20 | @GetAction("{id}") 21 | public Article show(String id) { 22 | return findById(id); 23 | } 24 | 25 | @PostAction 26 | public Article create(Article article) { 27 | return save(article); 28 | } 29 | ... 30 | } 31 | 32 | } 33 | ``` 34 | 35 | 这里我们看到可以通过 `POST /article`向服务提交article数据。假设我提交的数据是: 36 | 37 | ```json 38 | { 39 | "title": "How to control JSON view in Actframework", 40 | "content": "BlahBlah", 41 | "author": "Gelin Luo", 42 | "language": "Java", 43 | "framework": "Actframework", 44 | "tags" : [ 45 | {"name": "java"}, 46 | {"name": "mvc"}, 47 | {"name": "json"} 48 | ] 49 | } 50 | ``` 51 | 52 | 我可以得到类似下面的返回: 53 | 54 | ```json 55 | { 56 | "id": "58a6409ab6c6fe2138b67f10", 57 | "_created": "17/02/2017 11:15:22 AM", 58 | "content": "BlahBlah", 59 | "v": 1, 60 | "language": "Java", 61 | "author": "Gelin Luo", 62 | "title": "How to control JSON view in Actframework", 63 | "_modified": "17/02/2017 11:15:22 AM", 64 | "framework": "Actframework", 65 | "tags": [ 66 | { 67 | "name": "java" 68 | }, 69 | { 70 | "name": "mvc" 71 | }, 72 | { 73 | "name": "json" 74 | } 75 | ] 76 | } 77 | ``` 78 | 79 | 当我发出`GET /article`请求时,`Article.Service.list()`方法会响应并返回所有的article列表: 80 | 81 | ```json 82 | [ 83 | { 84 | "id": "58a6409ab6c6fe2138b67f10", 85 | "_created": "17/02/2017 11:15:22 AM", 86 | "content": "BlahBlah", 87 | "v": 1, 88 | "language": "Java", 89 | "author": "Gelin Luo", 90 | "title": "How to control JSON view in Actframework", 91 | "_modified": "17/02/2017 11:15:22 AM", 92 | "framework": "Actframework", 93 | "tags": [ 94 | { 95 | "name": "java" 96 | }, 97 | { 98 | "name": "mvc" 99 | }, 100 | { 101 | "name": "json" 102 | } 103 | ] 104 | } 105 | ] 106 | ``` 107 | 108 | 那如果我想控制返回列表的数据,让每项只返回`author`和`title`,我可以在`list()`方法上面添加注解`PropertySpec`: 109 | 110 | ```java 111 | @GetAction 112 | @act.util.PropertySpec("author,title") 113 | public Iterable
list() { 114 | return findAll(); 115 | } 116 | ``` 117 | 118 | 然后再发出`GET /article`请求,就可以得到下面的响应了: 119 | 120 | ```json 121 | [ 122 | { 123 | "author": "Gelin Luo", 124 | "title": "How to control JSON view in Actframework" 125 | } 126 | ] 127 | ``` 128 | 129 | 我可以在`Article.Service.show(String)`方法上采用类似的方法来定义需要返回的字段。有人提到过如果想让前端向后端在请求中传递需要的字段该怎么办,下面是Actframework提供的方法: 130 | 131 | 将`show(String)`方法做一点改动 132 | 133 | 从 134 | 135 | ```java 136 | @GetAction("{id}") 137 | public Article show(String id) { 138 | return findById(id); 139 | } 140 | ``` 141 | 142 | 变为 143 | 144 | ```java 145 | @GetAction("{id}") 146 | public Article show(String id, String fields) { 147 | PropertySpec.current.set(fields); 148 | return findById(id); 149 | } 150 | ``` 151 | 152 | 然后就可以从前端在请求中加载`fields`参数了: 153 | 154 | ``` 155 | GET /article/58a6409ab6c6fe2138b67f10?fields=-tags,-content,-_created 156 | ``` 157 | 158 | 上面的请求表示从返回JSON结果中去掉`tags`, `content`,和`_created`三个字段 159 | 160 | 返回结果将会是: 161 | 162 | ```json 163 | { 164 | "id": "58a6409ab6c6fe2138b67f10", 165 | "v": 1, 166 | "language": "Java", 167 | "author": "Gelin Luo", 168 | "title": "How to control JSON view in Actframework", 169 | "_modified": "17/02/2017 11:15:22 AM", 170 | "framework": "Actframework" 171 | } 172 | ``` 173 | 174 | 该博客的源码在码云上: 175 | 176 | https://git.oschina.net/greenlaw110/blog_json_view_control 177 | -------------------------------------------------------------------------------- /en/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | #### How is the performance of ActFramework 4 | 5 | [TechEmpower Benchmark](https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=fortune) is well known performance benchmark project for web tech stacks. Click [here](techempower/r14.md) to see how ActFramework performs in comparing to most JVM fullstack frameworks in TechEmpower Benchmark tests 6 | 7 | #### Why do I see the "App not found" issue 8 | 9 | I am using Intellij IDEA and when I start the "HelloWorld" sample application in the IDE, I got the following error stack: 10 | 11 | ``` 12 | Exception in thread "main" org.osgl.exception.UnexpectedException: App not found. Please make sure your app start directory is correct 13 | at act.Act.start(Act.java:307) 14 | at act.Act.startApp(Act.java:269) 15 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 16 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 17 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 18 | at java.lang.reflect.Method.invoke(Method.java:498) 19 | at act.boot.app.RunApp.start(RunApp.java:64) 20 | at act.Act.start(Act.java:610) 21 | at demo.helloworld.HelloWorldApp.main(HelloWorldApp.java:24) 22 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 23 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 24 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 25 | at java.lang.reflect.Method.invoke(Method.java:498) 26 | at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) 27 | ``` 28 | 29 | ##### Answer 30 | 31 | You need to update the Run configuration and make sure working directory is set correctly: 32 | ![image](https://cloud.githubusercontent.com/assets/216930/23855130/a2136556-0848-11e7-8184-2433004b123b.png) 33 | 34 | #### I got `java.security.InvalidKeyException: Illegal key size`, what's that 35 | 36 | ``` 37 | 13:01:06.851 [XNIO-1 task-17] ERROR a.a.u.AppCrypto - Cannot encrypt/decrypt! Please download Java Crypto Extension pack from Oracle: http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136007.html 38 | 13:01:06.851 [XNIO-1 task-17] ERROR a.h.b.c.RequestHandlerProxy - Error handling request 39 | org.osgl.exception.UnexpectedException: java.security.InvalidKeyException: Illegal key size 40 | at org.osgl.util.E.unexpected(E.java:100) 41 | at org.osgl.util.Crypto.encryptAES(Crypto.java:183) 42 | at act.app.util.AppCrypto.encrypt(AppCrypto.java:71) 43 | at act.app.App.encrypt(App.java:659) 44 | at act.util.SessionManager$CookieResolver.dissolveIntoCookieContent(SessionManager.java:321) 45 | at act.util.SessionManager$CookieResolver.dissolveSession(SessionManager.java:217) 46 | at act.util.SessionManager.dissolveSession(SessionManager.java:78) 47 | at act.app.ActionContext.dissolveSession(ActionContext.java:849) 48 | at act.app.ActionContext.dissolve(ActionContext.java:697) 49 | at act.handler.builtin.controller.RequestHandlerProxy.onResult(RequestHandlerProxy.java:241) 50 | at act.handler.builtin.controller.RequestHandlerProxy.handle(RequestHandlerProxy.java:177) 51 | at act.handler.DelegateRequestHandler.handle(DelegateRequestHandler.java:27) 52 | at act.route.Router$ContextualHandler.handle(Router.java:935) 53 | at act.xio.NetworkHandler$1.run(NetworkHandler.java:78) 54 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 55 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 56 | at java.lang.Thread.run(Thread.java:745) 57 | Caused by: java.security.InvalidKeyException: Illegal key size 58 | at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1034) 59 | at javax.crypto.Cipher.implInit(Cipher.java:800) 60 | at javax.crypto.Cipher.chooseProvider(Cipher.java:859) 61 | at javax.crypto.Cipher.init(Cipher.java:1370) 62 | at javax.crypto.Cipher.init(Cipher.java:1301) 63 | at org.osgl.util.Crypto.encryptAES(Crypto.java:174) 64 | ... 15 common frames omitted 65 | ``` 66 | 67 | ##### Answer 68 | 69 | You need to install JCE (Java Cryptography Extension). Please google `jce java` and find the link to oracle website. Follow the instruction to download and install 70 | 71 | -------------------------------------------------------------------------------- /cn/reference/bootstrap.md: -------------------------------------------------------------------------------- 1 | #附录 1 启动 ActFramework 2 | 3 | 本文讲述应用如何通过调用 `Act.start()` 接口启动 ActFramework 并设置应用的名字, 版本以及扫描包 4 | 5 | * [1. 启动过程](#bootstrap) 6 | * [2. AppDescriptor](#app_desc) 7 | * [2.1 .version 文件](#dot_version) 8 | * [2.2 名字](#setup_appname) 9 | * [2.2.1 短名字](#short_id) 10 | * [2.3 版本](#version) 11 | * [2.4 扫描包](#scan_package) 12 | 13 | ## 1. 启动过程 14 | 15 | `Act.start()` 是 ActFramework 应用启动的入口函数, 调用该方法之后会触发一系列操作: 16 | 17 | 1. 通过调用栈获得调用类名字, 比如 `com.mycom.myproj.AppEntry` 18 | 2. 获得该类的 `AppDescriptor` 19 | 3. 从 `AppDescriptor` 获得包名并将其设定在 `scan_package` 系统属性 (System.properties) 上面备用 20 | 4. 通过以下规则推断应用启动模式: 21 | - 4.1 如果发现 `app.mode` 系统设定, 则使用该设定 22 | - 4.2 如果发现应用从 jar 文件启动, 则直接设定 `app.mode` 为 `prod` 23 | - 4.3 如果发现应用从 classes 目录下启动则依据 `profile` 设定推断: 24 | * 4.3.1 如果发现 `profile` 系统设定为 `prod`, 则设定模式为 `prod` 25 | * 4.3.2 如果发现 `profile` 系统设定为 `dev`, 则设定模式为 `dev` 26 | * 4.3.3 所以其他情况均推断 `app.mode` 为 `dev` 27 | 5. 在控制台上打印 Banner 28 | 6. 初始化各种基础服务,包括 29 | * 性能统计 30 | * 插件管理器 31 | * 数据源管理器 32 | * 字节码增强器管理器 33 | * 视图 (View) 管理器 34 | * 自动加载各种插件 (Plugin) 35 | * 网络层 36 | * 应用程序管理器 37 | 7. 启动应用程序 38 | - 7.1 扫描应用程序文件结构并获得 `App` 实例 39 | - 7.2 构建应用, 包括准备 target 目录并拷贝资源文件 40 | - 仅对 dev 模式启用 41 | - 7.3 调用 `App.refresh`, 这里是应用的自举过程,包括一下步骤: 42 | * 清除上次应用启动状态 - 仅对 dev 模式有效 43 | * 初始化 SingletonManager 44 | * 初始化 EventBus 45 | * 加载 AppConfig 46 | * 加载 JobManager 47 | * 初始化 Router 48 | * 加载系统内置处理器路由 49 | * 初始化 CLI 服务 50 | * 初始化 WebSocket 连接管理器 51 | * 初始化 DbService 管理器 52 | - 这里会启动各个数据源的初始化线程 53 | * 初始化并加载字节码扫描器管理器 54 | * 初始化 AppClassLoader 55 | * 初始化 Cache 56 | * 扫描字节码 57 | * Hook 到 Act 的视图管理器 58 | * 加载依赖注入容器 59 | * 初始化 SessionManager 60 | * 当所有的数据源启动完成之后发出应用启动过程完成的事件 61 | 8. 获得应用的 http 端口并启动网络层 62 | 9. 获得进程 pid 并写入 pid 文件 63 | 64 | ## 2. AppDescrptior 65 | 66 | 在上面的启动过程中我们提到了一个概念 `AppDescriptor`, ActFramework 使用 `AppDescriptor` 描述应用的以下属性: 67 | 68 | 1. 名字 69 | 2. 版本 70 | 3. 扫描包 71 | 72 | ### 2.1 .version 文件 73 | 74 | 在继续讲述这三个方面之前需要提到一个特殊文件: `.version`. 如果使用 ActFramework 提供的 maven archetype, 会在项目的 `src/main/resources//` 下面生成一个名为 `.version` 的文件. 加入应用的 package 为 `com.mycom.myproj`, 该文件的路径为 `src/main/resources/com/mycom/myproj/.version`, 其内容为: 75 | 76 | ``` 77 | artifact=${project.artifactId} 78 | version=${project.version} 79 | build=${buildNumber} 80 | ``` 81 | 82 | 其中三个变量会在 maven 构建的时候被替换为实际值: 83 | 84 | * `project.artifactId` 替换为 pom.xml 文件中的 artifactId 85 | * `project.version` 替换为 pom.xml 文件中定义的 version 86 | * `buildNumber` 替换为 pom.xml 中 maven-buildnumber-plugin 生成的 buildnumnber 87 | 1. 如果项目托管在 git 下面, 则使用 git commit 的 hash 作为 buildnumber, 否则 88 | 2. 使用 build 时候的日期时间作为 buildnumber 89 | 90 | 这个 .version 文件帮助 ActFramework 获得默认的名字和应用的版本号. 关于该文件的详细描述, 参见 [osgl-version 项目](https://github.com/osglworks/java-version) 91 | 92 | ### 2.1 名字 93 | 94 | ActFramework 为每个应用提供默认名字, 也可以由应用在调用 `Act.start()` 的时候指定名字: 95 | 96 | ```java 97 | Act.start("My Awesome App"); 98 | ``` 99 | 100 | 如果没有指定名字, Act 通过下面的操作推断名字: 101 | 102 | 1. 检查是否有 .version 文件, 如果存在则使用其中的 artifact 定义作为应用名字. 否则 103 | 2. 通过启动类的包名和类名生成名字 104 | 105 | #### 2.1.1 短名字 (short id) 106 | 107 | 当确认应用名字之后 ActFramework 会按照一下规则依据名字生成 shortId: 108 | 109 | 1. 如果名字为空或者 `MyApp`, 则使用 `act` 作为 shortId 110 | 2. 将名字以空格符分割, 并按照分割后数组长度不同采用不同算法生成 shortId: 111 | 3. 如果分割后只有一个字串 (即名字中没有空格), 则截取名字前三个字符作为 shortId 112 | 4. 如果分割后有两个字串, 则每个字串截取 2 个字符并以 `-` 连接 113 | 5. 如果超过两个, 则取前三个字串,每个字串截取 1 个字符并以 `-` 连接 114 | 115 | ### 2.2 版本 116 | 117 | 如果 .version 文件存在, 则使用其中定义的 version 和 buildnumber 作为版本. 详情参见 [osgl-version 项目](https://github.com/osglworks/java-version) 118 | 119 | 如果 .version 文件不存在, 则使用 `unknown` 作为版本号 120 | 121 | ### 2.3 扫描包 122 | 123 | 可以在 `Act.start()` 方法中传入扫描包, 例如: 124 | 125 | ```java 126 | Act.start("my awesome app", "com.myproj;com.myproj2;..."); 127 | ``` 128 | 129 | 上面的代码中传入 `Act.start` 方法的第二参数 `"com.myproj;com.myproj2;..."` 即为扫描包. 130 | 131 | 如果没有指定扫描包, ActFramework 通过调用类的名字来获得扫描包. 假设类名为 `com.myproj.AppEntry` 扫描包为 `com.myproj` 132 | -------------------------------------------------------------------------------- /en/email.md: -------------------------------------------------------------------------------- 1 | # Sending Email 2 | 3 | Sending email is very simple in ActFramework. What you need to do is: 4 | 5 | 1. Use `Mailer` annotation to tag your class that contains send mail methods 6 | 1. Extend your class to `Mailer.Util` class or add a static import statement: 7 | 8 | `import static act.mail.Mailer.Util.*` 9 | 10 | ## Creating mailer class and methods 11 | 12 | ```java 13 | package com.mycom.myprj; 14 | 15 | import act.conf.AppConfig; 16 | import act.job.Every; 17 | import act.mail.Mailer; 18 | import org.osgl.logging.*; 19 | import org.osgl.util.E; 20 | import org.osgl.util.S; 21 | 22 | import javax.inject.Inject; 23 | 24 | /** 25 | * This class demonstrate how to write an email sender 26 | * in act framework. 27 | */ 28 | @Mailer 29 | public class PostOffice extends Mailer.Util { 30 | 31 | public void sendWelcome(Contact who) { 32 | to(who.getEmail()); 33 | subject("Welcome letter"); 34 | send(who); 35 | } 36 | 37 | public void sendBye(Contact who) { 38 | to(who.getEmail()); 39 | send(who); 40 | } 41 | 42 | } 43 | ``` 44 | 45 | ## Creating mailer templates 46 | 47 | Now that you have the mailer created, you can create the template corresponding to the mailer methods: 48 | 49 | 1. `src/main/resources/rythm/com/mycom/myprj/PostOffice/sendWelcome.html`: 50 | 51 | ```html 52 | 53 | 54 | 55 | @args com.mycom.myprj.Contact who 56 |

Welcome @who.getFirstName()!

57 |

Blah Blah

58 | 59 | 60 | ``` 61 | 62 | 1. `src/main/resources/rythm/com/mycom/myprj/PostOffice/sendBye.html`: 63 | 64 | ```html 65 | 66 | 67 | 68 | @args com.mycom.myprj.Contact who 69 |

Good bye @who.getFirstName()!

70 | 71 | 72 | ``` 73 | 74 | ## Calling mailer methods 75 | 76 | You can call the mailer method directly from any where, like the following controller code: 77 | 78 | ```java 79 | public class MyController { 80 | 81 | @Inject 82 | private PostOffice postOffice; 83 | 84 | @PostAction("/contact") 85 | public void createContact(Contact contact) { 86 | contactDao.save(contact); 87 | postOffice.sendWelcome(contact); 88 | } 89 | 90 | @PutAction("/contact/{contactId}/signOff") 91 | public void signOff(String contactId) { 92 | Contact contact = contactDao.findById(contactId); 93 | contact.signOff(); 94 | contactDao.save(contact); 95 | postOffice.sendBye(contact); 96 | } 97 | } 98 | ``` 99 | 100 | ## Calling mailer method asynchronously 101 | 102 | Usually executing mailer method involves IPC to external services (e.g. your SMTP server), which could be time consuming. So you would prefer to execute mailer method asynchrously and return back immediately. You can use ActFramework's event dispatching mechanism to achieve that. 103 | 104 | 1. Mark your mailer methods to be an event handler 105 | 106 | ```java 107 | @On(value = "contact-created", async = true) 108 | public void sendWelcome(Contact who) { 109 | to(who.getEmail()); 110 | subject("Welcome letter"); 111 | send(who); 112 | } 113 | 114 | @On(value = "contact-signed-off", async = true) 115 | public void sendBye(Contact who) { 116 | to(who.getEmail()); 117 | send(who); 118 | } 119 | ``` 120 | 121 | 1. Trigger event instead of calling mailer method directly: 122 | 123 | ```java 124 | public class MyController { 125 | 126 | @Inject 127 | EventBus eventBus; 128 | 129 | @PostAction("/contact") 130 | public void createContact(Contact contact) { 131 | contactDao.save(contact); 132 | eventBus.trigger("contact-created", contact); 133 | } 134 | 135 | @PutAction("/contact/{contactId}/signOff") 136 | public void signOff(String contactId) { 137 | Contact contact = contactDao.findById(contactId); 138 | contact.signOff(); 139 | contactDao.save(contact); 140 | eventBus.trigger("contact-signed-off", contact); 141 | } 142 | } 143 | ``` 144 | 145 | [Back to index](index.md) 146 | -------------------------------------------------------------------------------- /cn/social_link.md: -------------------------------------------------------------------------------- 1 | # 第十章 从社交服务认证 2 | 3 | 面向互联网的应用现在已经离不开社交服务了, 用户通常在多个社交服务中都保存有账号, 通过这些社交服务来提供用户认证可以让用户很容易注册到应用提供的服务中, 无需繁琐的用户注册和密码设置过程. 4 | 5 | ## 10.1 安装 SocialLink 插件 6 | 7 | ActFramework 核心库并没有提供社交服务认证的工具, 而是通过 SocialLink 插件来帮助应用实现这个功能. 使用 SocialLink 插件的办法是在项目的 `pom.xml` 文件中加入依赖: 8 | 9 | ```xml 10 | 11 | org.actframework 12 | act-social-link 13 | ${act-social-link.version} 14 | 15 | ``` 16 | 17 | **小提示** 如果使用了 `act-starter-parent` 作为项目的 parent, 则无需提供 `${act-social-link.version}` 18 | 19 | ## 10.2 配置 20 | 21 | 目前 SocialLink 插件提供了一下几种社交服务的集成: 22 | 23 | * Google 24 | * Facebook 25 | * GitHub 26 | * LinkedIn 27 | 28 | 如果需要使用这些社交服务, 应用首先需要到这些服务特定的页面注册自己的应用账号, 获得应用 `key` 和 `secret`. 下面是各种社交服务的账号管理页面地址: 29 | 30 | * Google - https://console.cloud.google.com/apis/credentials 31 | * Facebook - https://developers.facebook.com/apps 32 | * Github - https://github.com/settings/developers 33 | * LinkedIn - https://www.linkedin.com/developer/apps 34 | 35 | **注意** 每个社交服务注册页面中都有 origin 和 redirect URL 的设置, 其中的 origin 需要设置请求发起的站点名, 例如: 36 | 37 | ``` 38 | https://myapp.mycom.com 39 | ``` 40 | 41 | 而 redirect URL 则需要设置为下面的方式: 42 | 43 | ``` 44 | https://myapp.mycom.com/~/social/callback?provider= 45 | ``` 46 | 47 | 上面的 `provider-id` 可以是: 48 | 49 | * google 50 | * facebook 51 | * github 52 | * linkedin 53 | 54 | 在注册应用账号并获得服务 `key` 和 `secret` 之后将相关信息放进应用的配置文件中: 55 | 56 | ``` 57 | # 58 | # Twitter 59 | # 60 | social_link.twitter.key=your_consumer_key 61 | social_link.twitter.secret=your_consumer_secret 62 | 63 | # 64 | # Facebook 65 | # 66 | social_link.facebook.key=your_client_id 67 | social_link.facebook.secret=your_client_secret 68 | # this scope is the minimum SecureSocial requires. You can add more if required by your app. 69 | social_link.facebook.scope=email 70 | 71 | # 72 | # GitHub 73 | # 74 | social_link.github.key=your_consumer_key 75 | social_link.github.secret=your_consumer_secret 76 | 77 | # 78 | # Google 79 | # 80 | social_link.google.scope=openid email profile 81 | social_link.google.key=your_consumer_key 82 | social_link.google.secret=your_consumer_secret 83 | 84 | # 85 | # LinkedIn 86 | # 87 | social_link.linkedin.scope=r_emailaddress r_basicprofile 88 | social_link.linkedin.key=your_consumer_key 89 | social_link.linkedin.secret=your_consumer_secret 90 | ``` 91 | 92 | ## 10.3 使用 93 | 94 | 使用 `SocialLink` 的方法很简单: 95 | 96 | 1. 页面上需要提供社交服务认证链接 97 | 2. 后端应用提供处理用户 profile 的逻辑 98 | 99 | ### 10.3.1 社交认证链接 100 | 101 | 所有社交服务都使用相同的社交认证链接: 102 | 103 | ``` 104 | /~/social/start?provider= 105 | ``` 106 | 107 | 上面的 `provider-id` 可以是: 108 | 109 | * google 110 | * facebook 111 | * github 112 | * linkedin 113 | 114 | 下面是一种常见的社交服务链接页面组件: 115 | 116 | ```html 117 | 131 | ``` 132 | 133 | ### 10.3.2 处理用户的社交 Profile 134 | 135 | 当用户点击上节中讲到的社交链接之后, SocialLink 会向社交服务发起 OAuth 认证请求, 触发一系列交互过程, 包括用户在社交网上的登录以及认证确认. 这一系列操作都无需应用介入. 但认证最终结束之后 SocialLink 将会拿到用户的 Social Profile, 并触发一个 `SocialProfile.Fetched` 事件, 应用需要侦听该事件并完成用户登录. 下面是处理该事件的示例代码: 136 | 137 | ```java 138 | @OnEvent 139 | public void handleSocialLogin(SocialProfile.Fetched profileFetchedEvent, ActionContext context, User.Dao userDao) { 140 | SocialProfile profile = profileFetchedEvent.source(); 141 | String email = profile.getEmail(); 142 | User user = userDao.findByEmail(email); 143 | if (null == user) { 144 | // register new user 145 | user = new User(profile); 146 | userDao.save(user); 147 | } 148 | context.login(user.email); 149 | } 150 | ``` 151 | -------------------------------------------------------------------------------- /cn/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | #### ActFramework 的性能如何 4 | 5 | [TechEmpower Benchmark](https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=fortune) 是公认的 Web 技术栈性能测试项目. 点击 [此处](techempower/r14.md) 查看 ActFramework 和其他 JVM 全栈框架在 TechEmpower Benchmark 测试中的比较 6 | 7 | 8 | #### 我启动示例应用看到"App not found"报错是怎么回事 9 | 10 | 我使用Intellij IDEA加载了示例项目然后我运行"HelloWorld"程序的时候得到下面的错误堆栈: 11 | ``` 12 | Exception in thread "main" org.osgl.exception.UnexpectedException: App not found. Please make sure your app start directory is correct 13 | at act.Act.start(Act.java:307) 14 | at act.Act.startApp(Act.java:269) 15 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 16 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 17 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 18 | at java.lang.reflect.Method.invoke(Method.java:498) 19 | at act.boot.app.RunApp.start(RunApp.java:64) 20 | at act.Act.start(Act.java:610) 21 | at demo.helloworld.HelloWorldApp.main(HelloWorldApp.java:24) 22 | at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 23 | at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 24 | at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 25 | at java.lang.reflect.Method.invoke(Method.java:498) 26 | at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) 27 | ``` 28 | ##### 解决办法 29 | 30 | 你需要在IDEA的Run配置里面正确地设置"工作目录(working directory)": 31 | ![image](https://cloud.githubusercontent.com/assets/216930/23855130/a2136556-0848-11e7-8184-2433004b123b.png) 32 | 33 | #### 我不能构建示例程序, 下载依赖的时候提示出错 34 | 35 | 我在控制台上发现类似下面的警告信息 36 | 37 | ``` 38 | [WARN] The POM for io.undertow:undertow-core:jar:1.4.11.Final is invalid, transitive dependencies (if any) will not be available 39 | ``` 40 | 41 | ##### 解决办法 42 | 43 | 可能是你的网络的问题. 也许切换到阿里云的maven服务器可以解决这个问题. 另外你可能需要删除本地maven repository缓存的相关的库,再重新运行`mvn clean compile`一次, 就上面的错误警告来讲,你需要删除和undertow还有jboss xnio相关的本地缓存: 44 | 45 | ``` 46 | rm -rf ~/.m2/repository/io/undertow/ && rm -rf ~/.m2/repository/org/jboss/xnio 47 | ``` 48 | 49 | ## 我在控制台上看到这种出错信息`java.security.InvalidKeyException: Illegal key size`, 是怎么回事? 50 | 51 | ``` 52 | 13:01:06.851 [XNIO-1 task-17] ERROR a.a.u.AppCrypto - Cannot encrypt/decrypt! Please download Java Crypto Extension pack from Oracle: http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136007.html 53 | 13:01:06.851 [XNIO-1 task-17] ERROR a.h.b.c.RequestHandlerProxy - Error handling request 54 | org.osgl.exception.UnexpectedException: java.security.InvalidKeyException: Illegal key size 55 | at org.osgl.util.E.unexpected(E.java:100) 56 | at org.osgl.util.Crypto.encryptAES(Crypto.java:183) 57 | at act.app.util.AppCrypto.encrypt(AppCrypto.java:71) 58 | at act.app.App.encrypt(App.java:659) 59 | at act.util.SessionManager$CookieResolver.dissolveIntoCookieContent(SessionManager.java:321) 60 | at act.util.SessionManager$CookieResolver.dissolveSession(SessionManager.java:217) 61 | at act.util.SessionManager.dissolveSession(SessionManager.java:78) 62 | at act.app.ActionContext.dissolveSession(ActionContext.java:849) 63 | at act.app.ActionContext.dissolve(ActionContext.java:697) 64 | at act.handler.builtin.controller.RequestHandlerProxy.onResult(RequestHandlerProxy.java:241) 65 | at act.handler.builtin.controller.RequestHandlerProxy.handle(RequestHandlerProxy.java:177) 66 | at act.handler.DelegateRequestHandler.handle(DelegateRequestHandler.java:27) 67 | at act.route.Router$ContextualHandler.handle(Router.java:935) 68 | at act.xio.NetworkHandler$1.run(NetworkHandler.java:78) 69 | at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 70 | at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 71 | at java.lang.Thread.run(Thread.java:745) 72 | Caused by: java.security.InvalidKeyException: Illegal key size 73 | at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1034) 74 | at javax.crypto.Cipher.implInit(Cipher.java:800) 75 | at javax.crypto.Cipher.chooseProvider(Cipher.java:859) 76 | at javax.crypto.Cipher.init(Cipher.java:1370) 77 | at javax.crypto.Cipher.init(Cipher.java:1301) 78 | at org.osgl.util.Crypto.encryptAES(Crypto.java:174) 79 | ... 15 common frames omitted 80 | ``` 81 | 82 | ### 解决办法 83 | 84 | 你需要按照 JCE (Java Cryptography Extension). 搜索 `jce java` 然后找到并点击前往 oracle 网站的链接. 根据说明下载安装该软件 85 | 86 | -------------------------------------------------------------------------------- /cn/templating.md: -------------------------------------------------------------------------------- 1 | # 第七章 模板 2 | 3 | 目前ActFramework支持的模板引擎: 4 | 5 | * **[Rythm](http://rythmengine.org)** (框架内置) - 引擎ID: rythm 6 | * [Beetl](http://www.ibeetl.com) - 引擎ID: beetl 7 | * [FreeMarker](http://freemarker.apache.org) - 引擎ID: freemarker 8 | * [Mustache](https://github.com/spullara/mustache.java) - 引擎ID: mustache 9 | * [Thymeleaf](http://www.thymeleaf.org/) - 引擎ID: thymeleaf 10 | * [Velocity](http://velocity.apache.org) - 引擎ID: velocity 11 | 12 | **注意** 如果要使用非Rythm模板引擎需要在`pom.xml`文件中分别加入响应的依赖: 13 | 14 | Beelt依赖: 15 | 16 | ```xml 17 | 18 | org.actframework 19 | act-beetl 20 | 1.0.0 21 | 22 | ``` 23 | 24 | Freemarker依赖: 25 | 26 | ```xml 27 | 28 | org.actframework 29 | act-freemarker 30 | 1.0.1 31 | 32 | ``` 33 | 34 | Mustache依赖: 35 | 36 | ```xml 37 | 38 | org.actframework 39 | act-mustache 40 | 1.0.0 41 | 42 | ``` 43 | 44 | Thymeleaf依赖: 45 | 46 | ```xml 47 | 48 | org.actframework 49 | act-thymeleaf 50 | 1.0.0 51 | 52 | ``` 53 | 54 | 55 | Velocity依赖: 56 | 57 | ```xml 58 | 59 | org.actframework 60 | act-velocity 61 | 1.0.0 62 | 63 | ``` 64 | 65 | ## 7.1 模板文件的位置 66 | 67 | ActFramework依照以下管理访问模板文件: 68 | 69 | ``` 70 | /src/main/resources/{template-plugin-id}/{controller-class}/{action-method}.{fmt-suffix} 71 | ``` 72 | 73 | 假设你的控制器类是 74 | 75 | ```java 76 | package com.mycom.myprj; 77 | 78 | public class MyController { 79 | 80 | @GetAction("/") 81 | public void home() { 82 | } 83 | 84 | @GetAction("/foo") 85 | public Foo getFoo() { 86 | return Foo.instance(); 87 | } 88 | } 89 | ``` 90 | 91 | 对应与`home()`和`getFoo()`响应方法的两个模板文件分别为: 92 | 93 | 1. `/src/main/resources/rythm/com/mycom/myprj/MyController/home.html` 94 | 1. `/src/main/resources/rythm/com/mycom/myprj/MyController/getFoo.html` 95 | 96 | 如果你的应用需要对发送到`/foo`的请求支持`application/json`格式, 你可以创建json格式模板文件如下: 97 | 98 | ``` 99 | /src/main/resources/rythm/com/mycom/myprj/MyController/getFoo.json 100 | ``` 101 | 102 | ## 7.2 模板参数传递 103 | 104 | ActFramework使用ASM对响应方法做了增强,因此你不必像在Spring MVC应用中那样显示指定参数 105 | 106 | 一个SpringMVC的响应方法: 107 | 108 | ```java 109 | public String foo(String a, String b, int c, ModelMap modelMap) { 110 | modelMap.put("a", a); 111 | modelMap.put("b", b); 112 | modelMap.put("c", c); 113 | modelMap.put("abc", a + b + c); 114 | return "/path/to/the/template"; 115 | } 116 | ``` 117 | 118 | 用ActFramework重写上面的方法: 119 | 120 | ```java 121 | public Result foo(String a, String b, int c) { 122 | String abc = a + b + c; 123 | return render(a, b, c, abc); 124 | } 125 | ``` 126 | 127 | 在Rythm引擎中申明参数: 128 | 129 | ``` 130 | @args String a, String b, int c 131 |
132 | a = @a
133 | b = @b
134 | c = @c
135 | 
136 | ``` 137 | 138 | **注意** 其他模板不需要参数声明 139 | 140 | ### 7.2.1 向模板传递返回值 141 | 142 | 如果你的控制器需要向模板传递函数返回值 143 | 144 | ```java 145 | public Foo getFoo() { 146 | return dao.findOne(); 147 | } 148 | ``` 149 | 150 | 在模板中通过`result`名字来引用返回值: 151 | 152 | ``` 153 | @args Foo result 154 | Foo is @foo 155 | ``` 156 | 157 | ## 7.3 参考 158 | 159 | * [Beetl官网](http://www.ibeetl.com) 160 | * [Freemarker官网](http://freemarker.incubator.apache.org/) 161 | * [Velocity官网](http://velocity.apache.org) 162 | * [Mustache官网](https://github.com/spullara/mustache.java) 163 | * [Rythm官网](http://rythmengine.org) 164 | * [Thymeleaf官网](http://www.thymeleaf.org/) 165 | * [Velocity官网](http://velocity.apache.org) 166 | 167 | 168 | ## 7.4 演示项目 169 | 170 | 你可以在[github](https://github.com/actframework/act-demo-apps/tree/master/views)或者[码云](https://git.oschina.net/actframework/demo-apps/tree/master/views?dir=1&filepath=views)上访问模板引擎演示项目 171 | 172 | 该演示项目展示了: 173 | 174 | * 如何在项目中集成多个模板引擎 175 | * 在开发模式下各个模板引擎对错误显示的支持 176 | 177 | 178 | [返回目录](index.md) 179 | -------------------------------------------------------------------------------- /cn/ebean.md: -------------------------------------------------------------------------------- 1 | # 使用Ebean访问SQL Database 2 | 3 | ## 安装 4 | 5 | 在你的`pom.xml`文件中加上以下依赖: 6 | 7 | ```xml 8 | 9 | org.actframework 10 | act-ebean 11 | 0.1.1-SNAPSHOT 12 | 13 | ``` 14 | 15 | 根据你的数据库类型,你也需要加入相应的JDBC访问包的依赖。比如: 16 | 17 | ```xml 18 | 19 | com.h2database 20 | h2 21 | 1.4.178 22 | 23 | ``` 24 | 25 | ## 配置 26 | 27 | ``` 28 | # If you have only one DBPlugin in your class path, then 29 | # you do not need to specify the db.impl configuration 30 | db.impl=act.db.ebean.EbeanPlugin 31 | # database driver default to org.h2.Driver 32 | db.driver=... 33 | # database Url default to jdbc:h2:mem:tests 34 | db.url=... 35 | # username default is empty 36 | db.username=... 37 | # password default is empty 38 | db.password=... 39 | # If specified then app scan package will be used instead 40 | db.db2.agentPackage=act.doc.sample.** 41 | ``` 42 | 43 | ## 域模型 44 | 45 | 下面创建一个简单的域模型,该模型有三个字段: 46 | 47 | 1. `firstName` 48 | 1. `lastName` 49 | 1. `address` 50 | 51 | ```java 52 | package com.mycom.myprj; 53 | 54 | import act.db.DB; 55 | 56 | import javax.persistence.Entity; 57 | import javax.persistence.Id; 58 | 59 | 60 | @Entity(name = "ctct") 61 | public class Contact { 62 | @Id 63 | private Long id; 64 | private String fn; 65 | private String ln; 66 | private String addr; 67 | 68 | public long getId() { 69 | return null == id ? -1 : id; 70 | } 71 | 72 | public String getFirstName() { 73 | return fn; 74 | } 75 | 76 | public void setFirstName(String fn) { 77 | this.fn = fn; 78 | } 79 | 80 | public String getLastName() { 81 | return ln; 82 | } 83 | 84 | public void setLastName(String ln) { 85 | this.ln = ln; 86 | } 87 | 88 | public String getAddress() { 89 | return addr; 90 | } 91 | 92 | public void setAddress(String addr) { 93 | this.addr = addr; 94 | } 95 | } 96 | ``` 97 | 98 | **注意** 和Morphia访问层不同,Ebean访问层目前暂时不提供类似`MorphiaModel`的父类. 99 | 100 | ## 数据访问对象和CRUD 101 | 102 | 以下代码演示如何使用EbeanDao来进行CRUD操作: 103 | 104 | ```java 105 | package com.mycom.myprj; 106 | 107 | import act.app.App; 108 | import act.controller.Controller; 109 | import act.db.ebean.EbeanDao; 110 | import org.osgl.$; 111 | import org.osgl.mvc.annotation.DeleteAction; 112 | import org.osgl.mvc.annotation.GetAction; 113 | import org.osgl.mvc.annotation.PostAction; 114 | import org.osgl.mvc.annotation.PutAction; 115 | 116 | import javax.inject.Inject; 117 | 118 | @Controller("/ctct") 119 | public class ContactController extends Controller.Util { 120 | 121 | private EbeanDao dao; 122 | 123 | @Inject 124 | public ContactController(EbeanDao dao) { 125 | this.dao = dao; 126 | } 127 | 128 | @GetAction 129 | public Iterable list() { 130 | return dao.findAll(); 131 | } 132 | 133 | @PostAction 134 | public void create(Contact ctct) { 135 | dao.save(ctct); 136 | } 137 | 138 | @GetAction("/{id}") 139 | public Contact show(long id) { 140 | return dao.findById(id); 141 | } 142 | 143 | @PutAction("/{id}/addr") 144 | public void updateAddress(long id, String value) { 145 | Contact ctct = dao.findById(id); 146 | notFoundIfNull(ctct); 147 | ctct.setAddress(value); 148 | dao.save(ctct); 149 | } 150 | 151 | @DeleteAction 152 | public void delete(long id) { 153 | dao.deleteById(id); 154 | } 155 | 156 | } 157 | ``` 158 | 159 | ## 查询操作 160 | 161 | ```java 162 | // find by last name 163 | Iterable contacts = dao.findBy("firstName", firstName); 164 | 165 | // find by both first and last name 166 | Iterable contacts = dao.findBy("firstName,lastName", firstName, lastName); 167 | 168 | // find by firstName using regular expression 169 | Iterable contacts = dao.findBy("firstName", Pattern.compile(firstName)); 170 | ``` 171 | 172 | ## 扩展`EbeanDao` 173 | 174 | TBD 175 | 176 | ## 使用扩展的DAO类 177 | 178 | 假如你定义了扩展的DAO,你可以直接使用依赖注入来获取其实例: 179 | 180 | ```java 181 | //private EbeanDao dao = $.cast(app.dbServiceManager().dao(Contact.class)); 182 | private Contact.Dao dao = $.cast(app.dbServiceManager().dao(Contact.class)); 183 | ``` 184 | 185 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/techempower/r14.md: -------------------------------------------------------------------------------- 1 | # ActFramework 在 TechEmpower benchmark R14 中的表现 2 | 3 | [TechEmpower Benchmark](https://www.techempower.com/benchmarks/) 是业界公认的 web 框架/平台技术的PK场,刚刚发布的第14轮测试报告中比较了超过 140 中不同的语言/平台/框架技术,可以说这就是一个 Web 技术的嘉年华(Canival)。在第14轮中 TechEmpower 对所有的框架在同一个环境下实施了 6 种测试。这里我不打算罗列所有的 140 种技术,我将会比较所有 JVM 平台(包括 Java, Scala, Kotlin, Groovy 和 Closure) 上的**全栈** Web 框架在本次性能测试中的结果: 4 | 5 | **非全栈框架或平台(比如 undertow, netty等) 不在下面的比较结果中** 6 | 7 | ## JSON Serialization 测试结果比较 8 | 9 | 数据来源: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=json&l=6nq2qj&c=6 10 | 11 | ![image](https://cloud.githubusercontent.com/assets/216930/25933326/0f21e4f4-3659-11e7-8d7c-8de7296f25fc.png) 12 | 13 | ActFramework 在这个测试中以每秒 `334,157` 次的吞吐率排行第 4. 业界主流框架 Spring 的测试结果为每秒 `22,228` 次落在排行榜尾部 14 | 15 | ## Single Query 测试结果比较 16 | 17 | 数据来源: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=db&l=6nq2qj&c=6 18 | 19 | ![image](https://cloud.githubusercontent.com/assets/216930/25933471/4ddc6038-365a-11e7-9ad6-f6002affaf6c.png) 20 | 21 | 在这个测试中 ActFramework 派出了五名大将: 22 | 23 | * act-jdbc-pgsql (ActFramework 通过 RAW JDBC 访问 PostgreSQL 数据库): 每秒 `77,650` 次,排行第 5 24 | * actframework-pgsql (ActFramework 通过 BeetlSQL ORM 访问 PostgreSQL 数据库): 每秒 `73,185` 次 25 | * act-ebean-pgsql (ActFramework 通过 Ebean ORM 访问 PostgreSQL 数据库): 每秒 `71,029` 次 26 | * actframework-mongo (ActFramework 通过 Morphia 访问 MongoDB 数据库): 每秒 `66,664` 次 27 | * actframework-mysql (ActFramework 通过 Ebean ORM 访问 MySQL 数据库): 每秒 `53,800` 次 28 | * Spring (通过 Spring Data 访问 MySql 数据库): 每秒 `16,204` 次 29 | 30 | ## Multiple Queries 测试结果比较 31 | 32 | 数据来源:https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=query&l=6nq2qj&c=6 33 | 34 | ![image](https://cloud.githubusercontent.com/assets/216930/25933667/114268be-365c-11e7-959f-f9fdc8811805.png) 35 | 36 | 在这个测试中 act 和 PostgreSQL 的配合依旧十分出色,但 act 和 MySQL 的配合表现则不能让人满意: 37 | 38 | * act-jdbc-pgsql (ActFramework 通过 RAW JDBC 访问 PostgreSQL 数据库): 每秒 `7,696` 次,排行第 3 39 | * act-ebean-pgsql (ActFramework 通过 Ebean ORM 访问 PostgreSQL 数据库): 每秒 `7,392` 次 40 | * actframework-pgsql (ActFramework 通过 BeetlSQL ORM 访问 PostgreSQL 数据库): 每秒 `7,135` 次 41 | * actframework-mongo (ActFramework 通过 Morphia 访问 MongoDB 数据库): 每秒 `4,618` 次 42 | * Spring (通过 Spring Data 访问 MySql 数据库): 每秒 `2,415` 次 43 | * actframework-mysql (ActFramework 通过 Ebean ORM 访问 MySQL 数据库): 每秒 `2,113` 次 44 | 45 | ## Fortunes 测试结果比较 46 | 47 | 数据来源:https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=fortune&l=6nq2qj&c=6 48 | 49 | ![image](https://cloud.githubusercontent.com/assets/216930/25933820/6b880062-365d-11e7-9b39-c8a7a5452b38.png) 50 | 51 | * act-jdbc-pgsql (ActFramework 通过 RAW JDBC 访问 PostgreSQL 数据库): 每秒 `61,709` 次,排行第 4 52 | * act-ebean-pgsql (ActFramework 通过 Ebean ORM 访问 PostgreSQL 数据库): 每秒 `59,040` 次 53 | * actframework-mysql (ActFramework 通过 Ebean ORM 访问 MySQL 数据库): 每秒 `50,492` 次 54 | * actframework-pgsql (ActFramework 通过 BeetlSQL ORM 访问 PostgreSQL 数据库): 每秒 `33,163` 次 55 | * actframework-mongo (ActFramework 通过 Morphia 访问 MongoDB 数据库): 每秒 `25,720` 次 (有点差强人意) 56 | * Spring (通过 Spring Data 访问 MySql 数据库): 每秒 `24,047` 次 57 | 58 | 59 | ## Data Updates 测试结果比较 60 | 61 | 数据来源: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=update&l=6nq2qj&c=6 62 | 63 | ![image](https://cloud.githubusercontent.com/assets/216930/25934046/f60cc924-365e-11e7-8b9c-b4bc0e2e5422.png) 64 | 65 | 这个测试 PostgreSQL 依然坚挺, 而 MySQL 和 MongoDB 方面的表现都不理想 66 | 67 | * act-jdbc-pgsql (ActFramework 通过 RAW JDBC 访问 PostgreSQL 数据库): 每秒 ` 1,978` 次,排行第 2 68 | * actframework-pgsql (ActFramework 通过 BeetlSQL ORM 访问 PostgreSQL 数据库): 每秒 `1,851` 次 69 | * act-ebean-pgsql (ActFramework 通过 Ebean ORM 访问 PostgreSQL 数据库): 每秒 `1,689` 次 70 | * Spring (通过 Spring Data 访问 MySql 数据库): 每秒 `897` 次 71 | * actframework-mongo (ActFramework 通过 Morphia 访问 MongoDB 数据库): 每秒 `633` 次 72 | * actframework-mysql (ActFramework 通过 Ebean ORM 访问 MySQL 数据库): 每秒 `432` 次 73 | 74 | ## Plaintext 测试结果比较 75 | 76 | 数据来源: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=plaintext&l=6nq2qj&c=6 77 | 78 | ![image](https://cloud.githubusercontent.com/assets/216930/25934151/8af02dba-365f-11e7-918b-a26c63cfd549.png) 79 | 80 | 和第一个 JSON 测试一样, Plaintext的测试不需要数据库, ActFramework 在这个测试中表现也不错, 每秒 `601,390` 次, 排名第 3. Spring 的成绩是每秒 `128,577` 次. 81 | 82 | ## 参考 83 | 84 | 所有参加 TechEmpower 测试的源代码都可以在 [TechEmpower Benchmark 的 Github 项目](https://github.com/TechEmpower/FrameworkBenchmarks/) 中找到. 85 | 86 | * [ActFramework 测试项目代码](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/act) 87 | * [Spring 测试项目代码](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/spring) 88 | 89 | -------------------------------------------------------------------------------- /cn/reference/builtin-handler.md: -------------------------------------------------------------------------------- 1 | # 附录 3 ActFramework 内置服务 2 | 3 | ActFramework 提供了一些内置服务, 方便应用开发和使用. 所有的系统内置服务均被映射到以 `/~/` 开头的 URL 路径上. 4 | 5 | 1. [内置的响应器参考](#builtin-handler-reference) 6 | * [1.1 GET /~/apibook - 访问应用的 API 文档](#get-apibook) - 仅在开发模式有效 7 | * [1.2 GET /~/asset - 访问 ActFramework 内置 css/js 资源](#get-asset), 主要用于 ActFramework 在开发模式下的错误页面 8 | * [1.3 POST /~/i18n/locale - 改变当前用户会话的 Locale](#post-i18n-locale) 9 | * [1.4 POST /~/i18n/timezone - 是改变当前用户会话的时区](#post-i18n-timezone), 10 | * [1.5 GET /~/info - 显示应用信息](#get-info) 11 | * [1.6 GET /~/job/{id}/progress - 让应用查询特定后台任务的进度](#get-job-progress) - 这是一个 websocket 服务端点 12 | * [1.7 GET /~/pid - 显示应用进程号](#get-pid) 13 | * [1.8 GET /~/version - 显示应用版本信息](#get-version) 14 | * [1.9 GET /~/zen - 显示箴言列表](#get-zen) 15 | 2. [安全考虑与禁用内置服务](#disable-builtin-handler) 16 | 17 | ## 1. 内置服务参考 18 | 19 | ### 1.1 GET /~/apibook 20 | 21 | ActFramework 在开发模式下扫描程序源代码生成 API 文档. 访问 API 文档的坐标是 GET /~/apibook: 22 | 23 | ![image](https://user-images.githubusercontent.com/216930/38463411-c21ea972-3b3d-11e8-9739-2267f56e3419.png) 24 | 25 | ### 1.2 GET /~/asset 26 | 27 | ActFramework 内置了一些 css/js 资源文件, 主要用来支持: 28 | 29 | 1. 开发模式下的错误页面 30 | 1. 开发模式下的 API 文档. 31 | 32 | 一般情况下应用程序不应该访问 /~/asset/ 下面的资源 33 | 34 | ### 1.3 POST /~/i18n/locale 35 | 36 | 该端点为应用程序提供改变当前用户会话的 Locale 的服务. 具体信息请参考[国际化](../i18n.md) 37 | 38 | ### 1.4 POST /~/i18n/locale 39 | 40 | 该端点为应用程序提供改变当前用户会话时区的服务. 具体信息请参考[国际化](../i18n.md) 41 | 42 | ### 1.5 GET /~/info 43 | 44 | 提供当前应用的信息. 45 | 46 | 当通过浏览器直接访问该端点, 获得类似下面的信息: 47 | 48 | ``` 49 | _ _ _ _ _ 50 | |_| |_ | | / \ \ / / \ |_) | | \ 51 | | | |_ |_ |_ \_/ \/\/ \_/ | \ |_ |_/ 52 | 53 | powered by ActFramework r1.8.7-2f28 54 | 55 | version: v1.0-SNAPSHOT-180408_1425 56 | scan pkg: com.mycom.helloworld 57 | base dir: /tmp/1/helloworld 58 | pid: 31898 59 | profile: dev 60 | mode: DEV 61 | 62 | zen: Explicit is better than implicit. 63 | ``` 64 | 65 | 如果是 JavaScript 发出 JSON 类型的请求, 则获得类似下面的返回结果: 66 | 67 | ```json 68 | { 69 | "actVersion": "r1.8.7-2f28", 70 | "appName": "helloworld", 71 | "appVersion": "v1.0-SNAPSHOT-180408_1425", 72 | "baseDir": "/tmp/1/helloworld/.", 73 | "group": "", 74 | "mode": "DEV", 75 | "pid": "31898", 76 | "profile": "dev" 77 | } 78 | ``` 79 | 80 | ### 1.6 GET /~/job/{id}/progress 81 | 82 | 这是一个 websocket 服务端点, 为应用提供查询异步作业进度服务. 具体信息参考[作业调度](../job.md) 83 | 84 | ### 1.7 GET /~/pid 85 | 86 | 返回应用的进程号, 直接从浏览器访问结果示例: 87 | 88 | ``` 89 | 31898 90 | ``` 91 | 92 | 发出 `Accept=application/json` 的请求,返回结果为: 93 | 94 | ```json 95 | { 96 | "pid": "31898" 97 | } 98 | ``` 99 | 100 | ### 1.8 GET /~/version 101 | 102 | 返回当前应用的版本信息. 访问该段口得到的返回结果示例: 103 | 104 | ``` 105 | { 106 | "act": { 107 | "artifactId": "act", 108 | "buildNumber": "2f28", 109 | "packageName": "act", 110 | "projectVersion": "1.8.7", 111 | "unknown": false, 112 | "version": "r1.8.7-2f28" 113 | }, 114 | "app": { 115 | "artifactId": "helloworld", 116 | "buildNumber": "180408_1425", 117 | "packageName": "com.mycom.helloworld", 118 | "projectVersion": "1.0-SNAPSHOT", 119 | "unknown": false, 120 | "version": "v1.0-SNAPSHOT-180408_1425" 121 | } 122 | } 123 | ``` 124 | 125 | 这个结果和 HTTP Request 的 Accept 头设置没有关系. 126 | 127 | ### 1.9 GET /~/zen 128 | 129 | 返回应用的箴言列表. 这个服务端点对应用没有具体意义 130 | 131 | ## 2. 安全考虑与禁用内置服务 132 | 133 | 在 ActFramework 内置的服务端点中有些会泄露系统内部信息, 应该加以安全控制. 如果应用使用 [act-aaa](../aaa.md) 提供安全服务, 除下列服务外所有其他的内置服务端点都需要接受安全认证: 134 | 135 | * GET /~/apibook 136 | * GET /~/asset 137 | * POST /~/i18n/locale 138 | * POST /~/i18n/timezone 139 | * GET /~/zen 140 | 141 | 明确地讲, 访问下面的内置服务需要接受认证: 142 | 143 | * GET /~/info 144 | * GET /~/job/{id}/progress 145 | * GET /~/pid 146 | * GET /~/version 147 | 148 | 如果需要完全禁用内置服务, 在应用配置文件中加上: 149 | 150 | ``` 151 | built_in_req_handler=false 152 | ``` 153 | 154 | **注意** 下面的服务端点可能会影响应用的功能, 因此不能被禁用: 155 | 156 | * GET /~/asset 157 | * POST /~/i18n/locale 158 | * POST /~/i18n/timezone 159 | * GET /~/job/{id}/progress 160 | 161 | `apibook` 只能用于开发模式, 因此也不会被禁用. 162 | -------------------------------------------------------------------------------- /en/recipe/singleton.md: -------------------------------------------------------------------------------- 1 | # How to create Singleton in ActFramework 2 | 3 | Creating singleton class in Java is a [complicated topic](http://www.javaworld.com/article/2073352/core-java/simply-singleton.html) and deserve [long thread of discussion](http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java). 4 | 5 | #### Standard Java approach 6 | 7 | Fortunately since Java5 you got a very neat way to create singleton: 8 | 9 | ```java 10 | public enum MySingleton { 11 | INSTANCE; 12 | // your class implementation starts here 13 | public String foo() { 14 | return "foo" 15 | } 16 | } 17 | ``` 18 | 19 | To reference your singleton class simply refer to the `INSTANCE` field in the enum: 20 | 21 | ```java 22 | MySingleton.INSTANCE.foo(); 23 | ``` 24 | 25 | However if your singleton class needs to extend other class then you can't use the `enum` approach, and you probably need to go back to the classic approach as: 26 | 27 | ```java 28 | public class MySingleton extends MyBaseClass { 29 | public static final MySingleton INSTANCE = new MySingleton(); 30 | } 31 | ``` 32 | 33 | or if you want to keep the INSTANCE private: 34 | 35 | ```java 36 | public class MySingleton extends MyBaseClass { 37 | private static final MySingleton INSTANCE = new MySingleton(); 38 | public static MySingleton instance() { 39 | return INSTANCE; 40 | } 41 | public String foo() { 42 | return "foo" 43 | } 44 | } 45 | ``` 46 | 47 | or if you prefer to lazy load: 48 | 49 | ```java 50 | public class MySingleton extends MyBaseClass { 51 | private static volatile final MySingleton INSTANCE; 52 | public static MySingleton instance() { 53 | if (null == INSTANCE) { 54 | synchronized(MySingleton.class) { 55 | if (null == INSTANCE) { 56 | INSTANCE = new MySingleton(); 57 | } 58 | } 59 | } 60 | return INSTANCE; 61 | } 62 | } 63 | ``` 64 | 65 | **Tips** Make sure you have the `volatile` keyword in the `INSTANCE` declaration, otherwise you will get into the [trouble](http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html) 66 | 67 | #### ActFramework specific approach 68 | 69 | ActFramework provides you another approach to create singleton class: just add `javax.inject.Singleton` annotation to your class: 70 | 71 | ```java 72 | @javax.inject.Singleton 73 | public class MySingleton extends MyBaseClass { 74 | public String foo() { 75 | return "foo" 76 | } 77 | } 78 | ``` 79 | 80 | And to use your singleton class: 81 | 82 | ```java 83 | String s = act.app.App.instance().singleton(MySingleton.class).foo(); 84 | ``` 85 | 86 | Or if your application does not have super class to extend, you can extends your class to `act.util.SingletonBase`: 87 | 88 | ```java 89 | public class MySingleton extends act.util.SingletonBase { 90 | public String foo() { 91 | return "foo" 92 | } 93 | } 94 | ``` 95 | 96 | And to use your class: 97 | 98 | ```java 99 | MySingleton singleton = MySingleton.instance(); 100 | String s = singleton.foo(); 101 | 102 | // you can also use the App.singleton(Class) way to fetch your singleton: 103 | MySingleton singleton2 = act.app.App.instance().singleton(MySingleton.class); 104 | 105 | // or you can just ask App to give you an instance: 106 | MySingleton singleton3 = act.app.App.instance().getInstance(MySingleton.class); 107 | ``` 108 | 109 | ActFramework makes sure `singleton`, `singleton2` and `singleton3` is the same instance. You can also inject your singleton class into another class: 110 | 111 | ```java 112 | public class MySingletonConsumer { 113 | @Inject 114 | private MySingleton mySingleton; 115 | } 116 | ``` 117 | 118 | or into your [action handler](../controller.md#term) 119 | 120 | ```java 121 | @GetAction("/foo") 122 | public String sayFoo(MySingleton mySingleton) { 123 | return mySingleton.foo(); 124 | } 125 | ``` 126 | 127 | ## Summary 128 | 129 | * The easiest and most clean way to create singleton is to declare your singleton as `enum` 130 | * ActFramework understand `javax.inject.Singleton` annotation and collect your singleton instances in preload registry so that you can refer to your singleton instance through `act.app.App.instance().singleton(Class)` call 131 | * ActFramework can inject your singleton instance to certain context 132 | * ActFramework provides `SingletonBase` class and if your singleton class extends `SingletonBase` you can easily access your singleton instance via `MySingleton.instance()` call 133 | 134 | -------------------------------------------------------------------------------- /processor/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | true 10 | 11 | %date %highlight(%-5level) %cyan(%logger{5}@[%-4.30thread]) - %msg%n 12 | 13 | 14 | 15 | 16 | 17 | 18 | true 19 | 20 | %msg%n 21 | 22 | 23 | 24 | 25 | 26 | act.log 27 | 28 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 29 | 30 | 31 | 32 | /act.%i.log.zip 33 | 1 34 | 10 35 | 36 | 37 | 38 | 2MB 39 | 40 | 41 | 42 | 43 | 44 | e2e.log 45 | 46 | %msg%n 47 | 48 | 49 | 50 | 51 | act-db.log 52 | 53 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 54 | 55 | 56 | 57 | /act-db.%i.log.zip 58 | 1 59 | 10 60 | 61 | 62 | 63 | 2MB 64 | 65 | 66 | 67 | 68 | act-metric.log 69 | 70 | %d{yyyy-MM-dd_HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 71 | 72 | 73 | 74 | /act-metric.%i.log.zip 75 | 1 76 | 10 77 | 78 | 79 | 80 | 2MB 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /cn/morphia.md: -------------------------------------------------------------------------------- 1 | # 使用Morphia访问MongoDB数据库 2 | 3 | ## 安装 4 | 5 | 要在ActFramework应用中使用Morphia,请在`pom.xml`文件中添加以下依赖: 6 | 7 | ```xml 8 | 9 | org.actframework 10 | act-morphia 11 | 0.1.1-SNAPSHOT 12 | 13 | ``` 14 | 15 | ## 配置 16 | 17 | 简单的配置: 18 | 19 | ``` 20 | db.uri=mongodb://localhost/mydb 21 | ``` 22 | 23 | **小贴士** 你甚至不需要任何配置. ActFramework会自动连接到本地MongoDB服务器的test数据库 24 | 25 | 稍微复杂一点的配置: 26 | 27 | ``` 28 | db.url=mongodb://:@:,:,...,hostN:portN/dbname?replicaSet=...&connectTimeoutMS=... 29 | ``` 30 | 31 | 32 | ## 域模型 33 | 34 | 下面创建一个简单的域模型,该模型有两个字段: 35 | 36 | 1. `name` 37 | 1. `price` 38 | 39 | ```java 40 | package com.mycom.myprj; 41 | 42 | import org.mongodb.morphia.annotations.Entity; 43 | import act.db.morphia.MorphiaModel; 44 | 45 | @Entity("prod") 46 | public class Product extends MorphiaModel { 47 | private String name; 48 | private int price; 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public int getPrice() { 59 | return price; 60 | } 61 | 62 | public void setPrice(int price) { 63 | this.price = price; 64 | } 65 | } 66 | ``` 67 | 68 | ## 数据访问对象和CRUD 69 | 70 | 以下代码演示如何使用MorphiaDao来进行CRUD操作: 71 | 72 | ```java 73 | package com.mycom.myprj; 74 | 75 | import act.controller.Controller; 76 | import act.db.morphia.MorphiaDao; 77 | import org.bson.types.ObjectId; 78 | import org.osgl.mvc.annotation.GetAction; 79 | import org.osgl.mvc.annotation.PostAction; 80 | import org.osgl.mvc.annotation.PutAction; 81 | 82 | 83 | @Controller("/prod") 84 | public class ProductController extends Controller.Util { 85 | 86 | private MorphiaDao dao = Product.dao(); 87 | 88 | @GetAction 89 | public Iterable list() { 90 | return dao.findAll(); 91 | } 92 | 93 | @GetAction("/{id}") 94 | public Product show(String id) { 95 | return dao.findById(new ObjectId(id)); 96 | } 97 | 98 | @PostAction 99 | public void create(Product product) { 100 | dao.save(product); 101 | } 102 | 103 | @PutAction("/{id}/name") 104 | public void update(String id, String name) { 105 | Product product = dao.findById(new ObjectId(id)); 106 | notFoundIfNull(product); 107 | product.setName(name); 108 | dao.save(product); 109 | } 110 | 111 | @DeleteAction 112 | public void delete(String id) { 113 | dao.deleteById(new ObjectId(id)); 114 | } 115 | } 116 | ``` 117 | 118 | ## 查询操作 119 | 120 | ```java 121 | // find by name 122 | Iterable products = dao.findBy("name", name); 123 | 124 | // find all that price is less than 10000 125 | Iterable products = dao.findBy("price <", 100000); 126 | 127 | // find by name and price 128 | Iterable products = dao.findBy("name, price <", name, 100000); 129 | 130 | // find by name using regular expression 131 | Iterable products = dao.findBy("name", Pattern.compile("laptop")); 132 | ``` 133 | 134 | ## 使用扩展的DAO类 135 | 136 | 你可以根据需要扩展`MorphiaDao`类,并加入业务逻辑 137 | 138 | ```java 139 | @Entity("prod") 140 | public class Product extends MorphiaModel { 141 | private String name; 142 | private int price; 143 | 144 | public String getName() { 145 | return name; 146 | } 147 | 148 | public void setName(String name) { 149 | this.name = name; 150 | } 151 | 152 | public int getPrice() { 153 | return price; 154 | } 155 | 156 | public void setPrice(int price) { 157 | this.price = price; 158 | } 159 | 160 | public static class Dao extends MorphiaDao { 161 | 162 | public static final int LOW_PRICE = 10000; 163 | public static final int HIGH_PRICE = 999900; 164 | 165 | public Dao() { 166 | super(Product.class); 167 | } 168 | 169 | public Iterable findLowPriceProducts() { 170 | return findBy("price <", LOW_PRICE); 171 | } 172 | 173 | public Iterable findHighPriceProducts() { 174 | return findBy("price >", HIGH_PRICE); 175 | } 176 | } 177 | } 178 | 179 | ``` 180 | 181 | ## 使用扩展的DAO类 182 | 183 | 假如你定义了扩展的DAO,你可以使用同样的接口来获取其实例: 184 | 185 | ```java 186 | //private MorphiaDao dao = Product.dao(); 187 | private Product.Dao dao = Product.dao(); 188 | ``` 189 | 190 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/act-core-dependencies.md: -------------------------------------------------------------------------------- 1 | # ActFramework 依赖 2 | 3 | 下面的列表描述了ActFramework核心的依赖 4 | 5 | ``` 6 | +- org.actframework:act:jar:0.6.0-SNAPSHOT:compile 7 | | +- javax.inject:javax.inject:jar:1:compile ------------------------------------------------------- DI (JSR 330 API) 8 | | +- javax.enterprise:cdi-api:jar:1.2:compile ------------------------------------------------------ DI (CDI API, mainly to provides injection Scope annotations) 9 | | | +- javax.el:javax.el-api:jar:3.0.0:compile 10 | | | \- javax.interceptor:javax.interceptor-api:jar:1.2:compile 11 | | +- javax.validation:validation-api:jar:1.1.0.Final:compile --------------------------------------- Validation API 12 | | +- javax.mail:mail:jar:1.5.0-b01:compile --------------------------------------------------------- Java mail API 13 | | +- com.google.zxing:javase:jar:3.3.0:compile ------------------------------------------------------ For QR Code generation 14 | | | +- com.google.zxing:core:jar:3.3.0:compile 15 | | | +- com.beust:jcommander:jar:1.48:compile 16 | | | \- com.github.jai-imageio:jai-imageio-core:jar:1.3.1:compile 17 | | +- com.github.lalyos:jfiglet:jar:0.0.8:compile ---------------------------------------------------- For Banner font generation 18 | | +- org.actframework:act-asm:jar:0.1.0-SNAPSHOT:compile -------------------------------------------- For bytecode scan/enhancment 19 | | +- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile -------------------------------------- Validation implementation 20 | | | +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile 21 | | | \- com.fasterxml:classmate:jar:1.0.0:compile 22 | | +- com.alibaba:fastjson:jar:1.2.24:compile -------------------------------------------------------- For JSON support 23 | | +- io.undertow:undertow-core:jar:1.4.8.Final:compile ---------------------------------------------- For netowork 24 | | | +- org.jboss.xnio:xnio-api:jar:3.3.6.Final:compile 25 | | | \- org.jboss.xnio:xnio-nio:jar:3.3.6.Final:runtime 26 | | +- io.undertow:undertow-websockets-jsr:jar:1.4.8.Final:compile ------------------------------------ For network 27 | | | +- io.undertow:undertow-servlet:jar:1.4.8.Final:compile 28 | | | | +- org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec:jar:1.0.0.Final:compile 29 | | | | \- org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:jar:1.0.0.Final:compile 30 | | | \- org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:jar:1.1.0.Final:compile 31 | | +- com.squareup.okhttp3:okhttp:jar:3.4.1:compile -------------------------------------------------- For network (client side) 32 | | | \- com.squareup.okio:okio:jar:1.9.0:compile 33 | | +- jline:jline:jar:2.14.2:compile ----------------------------------------------------------------- For CLI 34 | | +- org.eclipse.jdt.core.compiler:ecj:jar:4.6.1:compile -------------------------------------------- For hot-reload 35 | | +- com.esotericsoftware:reflectasm:jar:1.11.3:compile --------------------------------------------- Fast reflection 36 | | | \- org.ow2.asm:asm:jar:5.0.4:compile 37 | | +- commons-fileupload:commons-fileupload:jar:1.3.2:compile ---------------------------------------- For multipart file parsing 38 | | | \- commons-io:commons-io:jar:2.2:compile 39 | | +- commons-codec:commons-codec:jar:1.10:compile --------------------------------------------------- For various encodig/decoding 40 | | +- joda-time:joda-time:jar:2.9.7:compile ---------------------------------------------------------- For datetime API and implementation 41 | | +- org.osgl:genie:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) -------------------------------------- The JSR 330 DI implementation 42 | | | \- org.osgl:osgl-logging:jar:0.7.0-SNAPSHOT:compile (version selected from constraint [0.7.0-SNAPSHOT,)) 43 | | | \- org.osgl:osgl-tool:jar:0.11.0-SNAPSHOT:compile (version selected from constraint [0.10.0-SNAPSHOT,0.11.0)) 44 | | +- org.osgl:osgl-mvc:jar:0.9.0-SNAPSHOT:compile (version selected from constraint [0.9.0-SNAPSHOT,)) ----------------------------------- The MVC API (Request/Response/Session/Flash/Result) 45 | | | \- org.osgl:osgl-http:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) 46 | | | +- org.osgl:osgl-storage:jar:0.8.0-SNAPSHOT:compile (version selected from constraint [0.8.0-SNAPSHOT,)) 47 | | | \- org.osgl:osgl-cache:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) 48 | | +- org.osgl:osgl-tool-ext:jar:0.1.0-SNAPSHOT:compile (version selected from constraint [0.1.0-SNAPSHOT,)) ------------------------------ The OSGL tool extension 49 | | \- org.rythmengine:rythm-engine:jar:1.1.7-SNAPSHOT:compile ----------------------------------------------------------------------------- The default template engine 50 | | +- com.stevesoft.pat:pat:jar:1.5.3:compile 51 | | +- org.apache.commons:commons-lang3:jar:3.4:compile 52 | | \- org.mvel:mvel2:jar:2.2.8.Final:compile 53 | ------------------------------------------------------------------------ 54 | ``` 55 | -------------------------------------------------------------------------------- /cn/recipe/resource_loading.md: -------------------------------------------------------------------------------- 1 | # 加载资源文件 2 | 3 | ## 1. 概念 4 | 5 | 存放在 `/src/main/resources` 目录或子目录下的文件是资源文件. 除了 ActFramework 需要的文件, 比如配置文件, 路由表等, 应用可以定义自己的资源文件, 并通过 `@LoadResource` 注解加载到程序中: 6 | 7 | ```java 8 | public class Foo { 9 | @LoadResource("name.list") 10 | private List nameList; 11 | } 12 | ``` 13 | 14 | 上面的代码指令 ActFramework 将 `resources/name.list` 文件读取到 `List nameList` 中, 每一行读取为 `nameList` 列表中的一个元素. 15 | 16 | ## 2. 从文本资源中加载 17 | 18 | ActFramework 支持从文本资源中加载到以下数据结构: 19 | 20 | * 字符串 21 | * 字符串列表 (每一行加载为列表中的一个元素) 22 | * `Properties` - 从 `.properties` 文件资源中加载 23 | * `Map` - 从类 `.properties` 文件资源中加载 24 | * `List` 25 | * 任意类型 - 从 JSON 或者 yaml 文件中加载 26 | 27 | ### 32 | 33 | ```java 34 | // snippet s2.1 35 | @LoadResource("myfile.txt") 36 | private String myStr; 37 | ``` 38 | 39 | ### 45 | 46 | ```java 47 | // snippet s2.2 48 | @LoadResource("myfile.txt") 49 | private List myLines; 50 | ``` 51 | 52 | ### 2.3 将文本资源加载到 `Properties` 结构 53 | 54 | 后缀名为 ".properties" 的资源文件可以加载进 `Properties` 类型数据中: 55 | 56 | 57 | 58 | ```java 59 | // snippet s2.3 60 | @LoadResource("foo.properties") 61 | private Properties foo; 62 | ``` 63 | 64 | ### 2.4 加载文本到 `Map` 结构 65 | 66 | 任何类似 properties 文件内容的文本 (包括 .properties 文件) 都可以加载进 `Map` 类型数据: 67 | 68 | 69 | 70 | ```java 71 | // snippet s2.4a 72 | @LoadResource("foo.properties") 73 | private Map foo; 74 | ``` 75 | 76 | 如果文本中的值是特定类型, 你可以声明为该类型. 假设你的文本文件 `int_values.txt` 内容如下: 77 | 78 | 79 | ``` 80 | # s2.4b 81 | one=1 82 | two=2 83 | ``` 84 | 85 | 你可以使用下面的方式类声明 `Map` 类型参数: 86 | 87 | 88 | ```java 89 | // snippet s2.4c 90 | @LoadResource("int_values.txt") 91 | private Map intValues; 92 | ``` 93 | 94 | ### 2.5 加载文本到 `List` 结构 95 | 96 | 在文本中每一行都可以转换为某个特定类型的情况下,可以将文本加载到非字符类型的 `List` 结构中, 例如资源文本为: 97 | 98 | ```text 99 | 1 100 | 2 101 | 3 102 | 4 103 | 5 104 | ``` 105 | 106 | 可以加载到 `List` 中: 107 | 108 | ```java 109 | @LoadResource("int.list") 110 | private List intList; 111 | ``` 112 | 113 | ### 2.6 加载 JSON 资源 114 | 115 | JSON 资源内容可以加载到任何符合文件内容结构的数据中: 116 | 117 | resource file: `chracters.json` 118 | 119 | 120 | ```json 121 | // snippet s2.6a 122 | [ 123 | { 124 | "username": "jamesbond", 125 | "level": 32 126 | }, 127 | { 128 | "username": "ethanhunt", 129 | "level": 30 130 | }, 131 | { 132 | "username": "jasonbourne", 133 | "level": 29 134 | } 135 | ] 136 | ``` 137 | 138 | The java POJO class 139 | 140 | 141 | ```java 142 | // snippet s2.6b 143 | @Data 144 | public class Character implements SimpleBean { 145 | public String username; 146 | public int level; 147 | } 148 | ``` 149 | 150 | Load the characters into a list of `Character`s 151 | 152 | 153 | ```java 154 | // snippet s2.6c 155 | @LoadResource("characters.json") 156 | private List characters; 157 | ``` 158 | 159 | ### 2.7 加载 YAML 资源 160 | 161 | 和 JSON 资源相似, YAML 资源也可以加载到任何符合文件内容结构的数据中: 162 | 163 | resource file: `chracters.yml` 164 | 165 | 166 | ```yaml 167 | # snippet s2.7a 168 | - username: jamesbond 169 | level: 32 170 | - username: ethanhunt 171 | level: 30 172 | - username: jasonbourne 173 | level: 29 174 | ``` 175 | 176 | The java POJO class 177 | 178 | 179 | ```java 180 | // snippet s2.7b 181 | @Data 182 | public class Character implements SimpleBean { 183 | public String username; 184 | public int level; 185 | } 186 | ``` 187 | 188 | Load the characters into a list of `Character`s 189 | 190 | 191 | ```java 192 | // snippet s2.7c 193 | @LoadResource("characters.yml") 194 | private List characters; 195 | ``` 196 | 197 | ## 3. 从二进制资源中加载 198 | 199 | 200 | ActFramework 支持从任何资源(包括二进制资源)中加载到以下数据结构: 201 | 202 | * `byte[]` 203 | * `java.nio.ByteBuffer` 204 | * `java.nio.file.Path` 205 | * `java.net.URL` 206 | * `java.io.File` 207 | * `java.io.InputStream` 208 | * `java.io.Reader` 209 | * `org.osgl.storage.ISObject` 210 | 211 | 212 | 213 | ```java 214 | // snippet s3a 215 | 216 | @LoadResource("myFile.pdf") 217 | private ISObject myStorageObject; 218 | 219 | @LoadResource("myFile.pdf") 220 | private File myFile; 221 | 222 | @LoadResource("myFile.pdf") 223 | private byte[] myFileBlob 224 | ``` -------------------------------------------------------------------------------- /en/act-core-dependencies.md: -------------------------------------------------------------------------------- 1 | # ActFramework dependencies 2 | 3 | The following list describes the dependencies of ActFramework core 4 | 5 | ``` 6 | +- org.actframework:act:jar:0.6.0-SNAPSHOT:compile 7 | | +- javax.inject:javax.inject:jar:1:compile ------------------------------------------------------- DI (JSR 330 API) 8 | | +- javax.enterprise:cdi-api:jar:1.2:compile ------------------------------------------------------ DI (CDI API, mainly to provides injection Scope annotations) 9 | | | +- javax.el:javax.el-api:jar:3.0.0:compile 10 | | | \- javax.interceptor:javax.interceptor-api:jar:1.2:compile 11 | | +- javax.validation:validation-api:jar:1.1.0.Final:compile --------------------------------------- Validation API 12 | | +- javax.mail:mail:jar:1.5.0-b01:compile --------------------------------------------------------- Java mail API 13 | | +- com.google.zxing:javase:jar:3.3.0:compile ------------------------------------------------------ For QR Code generation 14 | | | +- com.google.zxing:core:jar:3.3.0:compile 15 | | | +- com.beust:jcommander:jar:1.48:compile 16 | | | \- com.github.jai-imageio:jai-imageio-core:jar:1.3.1:compile 17 | | +- com.github.lalyos:jfiglet:jar:0.0.8:compile ---------------------------------------------------- For Banner font generation 18 | | +- org.actframework:act-asm:jar:0.1.0-SNAPSHOT:compile -------------------------------------------- For bytecode scan/enhancment 19 | | +- org.hibernate:hibernate-validator:jar:5.1.3.Final:compile -------------------------------------- Validation implementation 20 | | | +- org.jboss.logging:jboss-logging:jar:3.1.3.GA:compile 21 | | | \- com.fasterxml:classmate:jar:1.0.0:compile 22 | | +- com.alibaba:fastjson:jar:1.2.24:compile -------------------------------------------------------- For JSON support 23 | | +- io.undertow:undertow-core:jar:1.4.8.Final:compile ---------------------------------------------- For netowork 24 | | | +- org.jboss.xnio:xnio-api:jar:3.3.6.Final:compile 25 | | | \- org.jboss.xnio:xnio-nio:jar:3.3.6.Final:runtime 26 | | +- io.undertow:undertow-websockets-jsr:jar:1.4.8.Final:compile ------------------------------------ For network 27 | | | +- io.undertow:undertow-servlet:jar:1.4.8.Final:compile 28 | | | | +- org.jboss.spec.javax.servlet:jboss-servlet-api_3.1_spec:jar:1.0.0.Final:compile 29 | | | | \- org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:jar:1.0.0.Final:compile 30 | | | \- org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:jar:1.1.0.Final:compile 31 | | +- com.squareup.okhttp3:okhttp:jar:3.4.1:compile -------------------------------------------------- For network (client side) 32 | | | \- com.squareup.okio:okio:jar:1.9.0:compile 33 | | +- jline:jline:jar:2.14.2:compile ----------------------------------------------------------------- For CLI 34 | | +- org.eclipse.jdt.core.compiler:ecj:jar:4.6.1:compile -------------------------------------------- For hot-reload 35 | | +- com.esotericsoftware:reflectasm:jar:1.11.3:compile --------------------------------------------- Fast reflection 36 | | | \- org.ow2.asm:asm:jar:5.0.4:compile 37 | | +- commons-fileupload:commons-fileupload:jar:1.3.2:compile ---------------------------------------- For multipart file parsing 38 | | | \- commons-io:commons-io:jar:2.2:compile 39 | | +- commons-codec:commons-codec:jar:1.10:compile --------------------------------------------------- For various encodig/decoding 40 | | +- joda-time:joda-time:jar:2.9.7:compile ---------------------------------------------------------- For datetime API and implementation 41 | | +- org.osgl:genie:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) -------------------------------------- The JSR 330 DI implementation 42 | | | \- org.osgl:osgl-logging:jar:0.7.0-SNAPSHOT:compile (version selected from constraint [0.7.0-SNAPSHOT,)) 43 | | | \- org.osgl:osgl-tool:jar:0.11.0-SNAPSHOT:compile (version selected from constraint [0.10.0-SNAPSHOT,0.11.0)) 44 | | +- org.osgl:osgl-mvc:jar:0.9.0-SNAPSHOT:compile (version selected from constraint [0.9.0-SNAPSHOT,)) ----------------------------------- The MVC API (Request/Response/Session/Flash/Result) 45 | | | \- org.osgl:osgl-http:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) 46 | | | +- org.osgl:osgl-storage:jar:0.8.0-SNAPSHOT:compile (version selected from constraint [0.8.0-SNAPSHOT,)) 47 | | | \- org.osgl:osgl-cache:jar:0.5.0-SNAPSHOT:compile (version selected from constraint [0.5.0-SNAPSHOT,)) 48 | | +- org.osgl:osgl-tool-ext:jar:0.1.0-SNAPSHOT:compile (version selected from constraint [0.1.0-SNAPSHOT,)) ------------------------------ The OSGL tool extension 49 | | \- org.rythmengine:rythm-engine:jar:1.1.7-SNAPSHOT:compile ----------------------------------------------------------------------------- The default template engine 50 | | +- com.stevesoft.pat:pat:jar:1.5.3:compile 51 | | +- org.apache.commons:commons-lang3:jar:3.4:compile 52 | | \- org.mvel:mvel2:jar:2.2.8.Final:compile 53 | ------------------------------------------------------------------------ 54 | ``` 55 | -------------------------------------------------------------------------------- /en/templating.md: -------------------------------------------------------------------------------- 1 | # Templating 2 | 3 | At the moment ActFramework support the following template engines: 4 | 5 | * **[Rythm](http://rythmengine.org)** (built-in) - engine ID: rythm 6 | * [Beetl](http://www.ibeetl.com) - engine ID: beetl 7 | * [FreeMarker](http://freemarker.apache.org) - engine ID: freemarker 8 | * [Mustache](https://github.com/spullara/mustache.java) - engine ID: mustache 9 | * [Thymeleaf](http://www.thymeleaf.org/) - engine ID: thymeleaf 10 | * [Velocity](http://velocity.apache.org) - engine ID: velocity 11 | 12 | **Note** If app needs to use template engine other than the built-in rythm, it has to add the corresponding dependency into `pom.xml` file: 13 | 14 | For Beelt: 15 | 16 | ```xml 17 | 18 | org.actframework 19 | act-beetl 20 | 1.0.0 21 | 22 | ``` 23 | 24 | For Freemarker: 25 | 26 | ```xml 27 | 28 | org.actframework 29 | act-freemarker 30 | 1.0.1 31 | 32 | ``` 33 | 34 | For Mustache: 35 | 36 | ```xml 37 | 38 | org.actframework 39 | act-mustache 40 | 1.0.0 41 | 42 | ``` 43 | 44 | For Thymeleaf: 45 | 46 | ```xml 47 | 48 | org.actframework 49 | act-thymeleaf 50 | 1.0.0 51 | 52 | ``` 53 | 54 | 55 | For Velocity: 56 | 57 | ```xml 58 | 59 | org.actframework 60 | act-velocity 61 | 1.0.0 62 | 63 | ``` 64 | 65 | ## Location of template file 66 | 67 | ActFramework use the following pattern to look for template file: 68 | 69 | ``` 70 | /src/main/resources/{template-plugin-id}/{controller-class}/{action-method}.{fmt-suffix} 71 | ``` 72 | 73 | Suppose your controller class is 74 | 75 | ```java 76 | package com.mycom.myprj; 77 | 78 | public class MyController { 79 | 80 | @GetAction("/") 81 | public void home() { 82 | } 83 | 84 | @GetAction("/foo") 85 | public Foo getFoo() { 86 | return Foo.instance(); 87 | } 88 | } 89 | ``` 90 | 91 | The template file correspondign to `home()` and `getFoo()` methods are: 92 | 93 | 1. `/src/main/resources/rythm/com/mycom/myprj/MyController/home.html` 94 | 1. `/src/main/resources/rythm/com/mycom/myprj/MyController/getFoo.html` 95 | 96 | If the app needs to send request to `/foo` using `application/json` content type, then you can 97 | create JSON template file like: 98 | 99 | ``` 100 | /src/main/resources/rythm/com/mycom/myprj/MyController/getFoo.json 101 | ``` 102 | 103 | ## Passing render arguments to template 104 | 105 | ActFramework use ASM to enhance the action handler method, thus you don't need to explicitly specify render args 106 | as what you did in SpringMVC: 107 | 108 | 109 | ```java 110 | public String foo(String a, String b, int c, ModelMap modelMap) { 111 | modelMap.put("a", a); 112 | modelMap.put("b", b); 113 | modelMap.put("c", c); 114 | modelMap.put("abc", a + b + c); 115 | return "/path/to/the/template"; 116 | } 117 | ``` 118 | 119 | The same method written in ActFramework app: 120 | 121 | ```java 122 | public Result foo(String a, String b, int c) { 123 | String abc = a + b + c; 124 | return render(a, b, c, abc); 125 | } 126 | ``` 127 | 128 | You must declare template arguments in Rythm template: 129 | 130 | ``` 131 | @args String a, String b, int c 132 |
133 | a = @a
134 | b = @b
135 | c = @c
136 | 
137 | ``` 138 | 139 | **Note** If you are using other template engine, then you don't need to declare template arguments 140 | 141 | ## Passing return value to template 142 | 143 | If the action handler return an object like 144 | 145 | ```java 146 | public Foo getFoo() { 147 | return dao.findOne(); 148 | } 149 | ``` 150 | 151 | You can reference the returned value using argument name `result`: 152 | 153 | ``` 154 | @args Foo result 155 | Foo is @foo 156 | ``` 157 | 158 | ## Reference 159 | 160 | * [Beetl](http://www.ibeetl.com) 161 | * [Freemarker](http://freemarker.incubator.apache.org/) 162 | * [Velocity](http://velocity.apache.org) 163 | * [Mustache](https://github.com/spullara/mustache.java) 164 | * [Rythm](http://rythmengine.org) 165 | * [Thymeleaf](http://www.thymeleaf.org/) 166 | * [Velocity](http://velocity.apache.org) 167 | 168 | ### Demo project 169 | 170 | You can checkout the view demo project at [github](https://github.com/actframework/act-demo-apps/tree/master/views) or [码云](https://git.oschina.net/actframework/demo-apps/tree/master/views?dir=1&filepath=views) 171 | 172 | The demo project shows: 173 | 174 | * Integrate multiple template engines in your project 175 | * How different template engines present error info in dev mode 176 | 177 | [Back](index.md) 178 | -------------------------------------------------------------------------------- /en/techempower/r14.md: -------------------------------------------------------------------------------- 1 | # ActFramework in TechEmpower benchmark R14 2 | 3 | [TechEmpower Benchmark](https://www.techempower.com/benchmarks/) is the common known Web platform/framework performance benchmark project. There are over 140 technical stacks (frameworks/platfroms) of different languages spanning from PHP to c/c++, it is really a Canival of web technologies. There are 6 test types for all 140+ techs been implemented in R14. Here I am not going to list the results of all 140 techs, instead I will compare **full-stack** framework of all JVM lanauges below: 4 | 5 | **Note non-full-stack framework or platform (i.g. undertow, netty etc) is not listed in the comparison below 6 | 7 | ## JSON Serialization test result 8 | 9 | Data source: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=json&l=6nq2qj&c=6 10 | 11 | ![image](https://cloud.githubusercontent.com/assets/216930/25933326/0f21e4f4-3659-11e7-8d7c-8de7296f25fc.png) 12 | 13 | ActFramework ranks #4 in this test with a throughput at `334,157/s`. While the de-facto Java web framework Spring got `22,228/s` and fell at the rear part of the list. 14 | 15 | ## Single Query test result 16 | 17 | Data source: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=db&l=6nq2qj&c=6 18 | 19 | ![image](https://cloud.githubusercontent.com/assets/216930/25933471/4ddc6038-365a-11e7-9ad6-f6002affaf6c.png) 20 | 21 | As it touches database access ActFramework put 5 different candidates into this test: 22 | 23 | * act-jdbc-pgsql (ActFramework access PostgreSQL via RAW JDBC): `77,650/s` rank #5 24 | * actframework-pgsql (ActFramework access PostgreSQL via BeetlSQL ORM): `73,185/s` 25 | * act-ebean-pgsql (ActFramework access PostgreSQL via Ebean ORM): `71,029/s` 26 | * actframework-mongo (ActFramework access MongoDB via Morphia): `66,664/s` 27 | * actframework-mysql (ActFramework access MySQL via Ebean ORM): `53,800/s` 28 | * Spring (access MySQL via Spring Data): `16,204/s` 29 | 30 | ## Multiple Queries test result 31 | 32 | Data source:https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=query&l=6nq2qj&c=6 33 | 34 | ![image](https://cloud.githubusercontent.com/assets/216930/25933667/114268be-365c-11e7-959f-f9fdc8811805.png) 35 | 36 | Again Act/PostgreSQL pair got good ranks in this test, while the act/MySQL combination result is not satisfied 37 | 38 | * act-jdbc-pgsql (ActFramework access PostgreSQL via RAW JDBC): `7,696/s` rank #3 39 | * act-ebean-pgsql (ActFramework access PostgreSQL via Ebean ORM): `7,392/s` 40 | * actframework-pgsql (ActFramework access PostgreSQL via BeetlSQL ORM): `7,135/s` 41 | * actframework-mongo (ActFramework access MongoDB via Morphia): `4,618/s` 42 | * Spring (access MySQL via Spring Data): `2,415/s` 43 | * actframework-mysql (ActFramework access MySQL via Ebean ORM): `2,113/s` 44 | 45 | ## Fortunes test result 46 | 47 | Data source:https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=fortune&l=6nq2qj&c=6 48 | 49 | ![image](https://cloud.githubusercontent.com/assets/216930/25933820/6b880062-365d-11e7-9b39-c8a7a5452b38.png) 50 | 51 | * act-jdbc-pgsql (ActFramework access PostgreSQL via RAW JDBC): `61,709/s` rank #4 52 | * act-ebean-pgsql (ActFramework access PostgreSQL via Ebean ORM): `59,040/s` 53 | * actframework-mysql (ActFramework access MySQL via Ebean ORM): `50,492/s` 54 | * actframework-pgsql (ActFramework access PostgreSQL via BeetlSQL ORM): `33,163/s` 55 | * actframework-mongo (ActFramework access MongoDB via Morphia): `25,720/s` 56 | * Spring (access MySQL via Spring Data): `24,047/s` 57 | 58 | 59 | ## Data Updates test result 60 | 61 | Data source: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=update&l=6nq2qj&c=6 62 | 63 | ![image](https://cloud.githubusercontent.com/assets/216930/25934046/f60cc924-365e-11e7-8b9c-b4bc0e2e5422.png) 64 | 65 | PostgreSQL still perform good in this test, left MySQL and MongoDB not so cool. 66 | 67 | * act-jdbc-pgsql (ActFramework access PostgreSQL via RAW JDBC): `1,978/s` rank #2 68 | * actframework-pgsql (ActFramework access PostgreSQL via BeetlSQL ORM): `1,851/s` 69 | * act-ebean-pgsql (ActFramework access PostgreSQL via Ebean ORM): `1,689/s` 70 | * Spring (access MySQL via Spring Data): `897/s` 71 | * actframework-mysql (ActFramework access MySQL via Ebean ORM): `432/s` 72 | * actframework-mongo (ActFramework access MongoDB via Morphia): `633/s` 73 | 74 | ## Plaintext test result 75 | 76 | Data source: https://www.techempower.com/benchmarks/#section=data-r14&hw=ph&test=plaintext&l=6nq2qj&c=6 77 | 78 | ![image](https://cloud.githubusercontent.com/assets/216930/25934151/8af02dba-365f-11e7-918b-a26c63cfd549.png) 79 | 80 | Same with JSON test, Plaintext test does not require database access, ActFramework has reletively good rank in this test also, the throughput is `601,390/s`, rank #3. Spring got `128,577/s` in this test. 81 | 82 | ## Reference 83 | 84 | All TechEmpower test source code can be found at [TechEmpower Benchmark Github project](https://github.com/TechEmpower/FrameworkBenchmarks/) 85 | 86 | * [ActFramework test project](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/act) 87 | * [Spring test project](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Java/spring) 88 | 89 | -------------------------------------------------------------------------------- /en/ebean.md: -------------------------------------------------------------------------------- 1 | # Access SQL Database with Ebean 2 | 3 | ## Setup 4 | 5 | In order to use Ebean you need to add the following dependencies in your `pom.xml` file: 6 | 7 | ```xml 8 | 9 | org.actframework 10 | act-ebean 11 | 0.1.1-SNAPSHOT 12 | 13 | ``` 14 | 15 | You will also need to import your JDBC or database packages something like 16 | 17 | ```xml 18 | 19 | com.h2database 20 | h2 21 | 1.4.178 22 | 23 | ``` 24 | 25 | ## Configuration 26 | 27 | ``` 28 | # If you have only one DBPlugin in your class path, then 29 | # you do not need to specify the db.impl configuration 30 | db.impl=act.db.ebean.EbeanPlugin 31 | # database driver default to org.h2.Driver 32 | db.driver=... 33 | # database Url default to jdbc:h2:mem:tests 34 | db.url=... 35 | # username default is empty 36 | db.username=... 37 | # password default is empty 38 | db.password=... 39 | # If specified then app scan package will be used instead 40 | db.db2.agentPackage=act.doc.sample.** 41 | ``` 42 | 43 | ## Model 44 | 45 | Let's create a simple Contact model with three properties: 46 | 47 | 1. `firstName` 48 | 1. `lastName` 49 | 1. `address` 50 | 51 | ```java 52 | package com.mycom.myprj; 53 | 54 | import act.db.DB; 55 | 56 | import javax.persistence.Entity; 57 | import javax.persistence.Id; 58 | 59 | 60 | @Entity(name = "ctct") 61 | public class Contact { 62 | @Id 63 | private Long id; 64 | private String fn; 65 | private String ln; 66 | private String addr; 67 | 68 | public long getId() { 69 | return null == id ? -1 : id; 70 | } 71 | 72 | public String getFirstName() { 73 | return fn; 74 | } 75 | 76 | public void setFirstName(String fn) { 77 | this.fn = fn; 78 | } 79 | 80 | public String getLastName() { 81 | return ln; 82 | } 83 | 84 | public void setLastName(String ln) { 85 | this.ln = ln; 86 | } 87 | 88 | public String getAddress() { 89 | return addr; 90 | } 91 | 92 | public void setAddress(String addr) { 93 | this.addr = addr; 94 | } 95 | } 96 | ``` 97 | 98 | **Note** unlike Morphia which allows you to extend your Model class to `MorphiaModel`, Ebean plugin does not support that at the moment. 99 | 100 | ## DAO and CRUD 101 | 102 | Now that the model has been defined, let's take a look at how to use built-in DAO to do CRUD operations. 103 | 104 | ```java 105 | package com.mycom.myprj; 106 | 107 | import act.app.App; 108 | import act.controller.Controller; 109 | import act.db.ebean.EbeanDao; 110 | import org.osgl.$; 111 | import org.osgl.mvc.annotation.DeleteAction; 112 | import org.osgl.mvc.annotation.GetAction; 113 | import org.osgl.mvc.annotation.PostAction; 114 | import org.osgl.mvc.annotation.PutAction; 115 | 116 | import javax.inject.Inject; 117 | 118 | @Controller("/ctct") 119 | public class ContactController extends Controller.Util { 120 | 121 | private EbeanDao dao; 122 | 123 | @Inject 124 | public ContactController(EbeanDao dao) { 125 | this.dao = dao; 126 | } 127 | 128 | @GetAction 129 | public Iterable list() { 130 | return dao.findAll(); 131 | } 132 | 133 | @PostAction 134 | public void create(Contact ctct) { 135 | dao.save(ctct); 136 | } 137 | 138 | @GetAction("/{id}") 139 | public Contact show(long id) { 140 | return dao.findById(id); 141 | } 142 | 143 | @PutAction("/{id}/addr") 144 | public void updateAddress(long id, String value) { 145 | Contact ctct = dao.findById(id); 146 | notFoundIfNull(ctct); 147 | ctct.setAddress(value); 148 | dao.save(ctct); 149 | } 150 | 151 | @DeleteAction 152 | public void delete(long id) { 153 | dao.deleteById(id); 154 | } 155 | 156 | } 157 | ``` 158 | 159 | **Note** the different between Morphia plugin and Ebean plugin on how to get the Dao instance. 160 | 161 | ## Search 162 | 163 | Act provide a set of search methods in `Dao` interface: 164 | 165 | ```java 166 | // find by last name 167 | Iterable contacts = dao.findBy("firstName", firstName); 168 | 169 | // find by both first and last name 170 | Iterable contacts = dao.findBy("firstName,lastName", firstName, lastName); 171 | 172 | // find by firstName using regular expression 173 | Iterable contacts = dao.findBy("firstName", Pattern.compile(firstName)); 174 | ``` 175 | 176 | ## Extend `EbeanDao` 177 | TBD 178 | 179 | ## Using extended DAO class 180 | 181 | Once you have extended the DAO, you can use it following the same way as shown above, just change the type as shown below: 182 | 183 | ```java 184 | //private EbeanDao dao = $.cast(app.dbServiceManager().dao(Contact.class)); 185 | private Contact.Dao dao = $.cast(app.dbServiceManager().dao(Contact.class)); 186 | ``` 187 | 188 | So ActFramework detects your implementation of the DAO and will use that class instead of the standard one. 189 | 190 | [Back to index](index.md) -------------------------------------------------------------------------------- /en/recipe/json-response.md: -------------------------------------------------------------------------------- 1 | # Control your JSON response details in ActFramework 2 | 3 | This recipe describes how to control the JSON response fields in Act application. It is not unusual that 4 | we require return different view of the same data model in different scenarios. A typical case of 5 | controller data model fields in JSON response is to take `password` fields out of `User` entity 6 | in the response. Now let's take a look at how to manage JSON response in Act application 7 | 8 | First let's create a data model class `Article` and the RESTful service endpoints for the article resource: 9 | 10 | ```java 11 | @Entity("article") 12 | public class Article extends MorphiaAdaptiveRecord
{ 13 | 14 | @Controller("article") 15 | public static class Service extends MorphiaDao
{ 16 | 17 | @GetAction 18 | public Iterable
list() { 19 | return findAll(); 20 | } 21 | 22 | @GetAction("{id}") 23 | public Article show(String id) { 24 | return findById(id); 25 | } 26 | 27 | @PostAction 28 | public Article create(Article article) { 29 | return save(article); 30 | } 31 | ... 32 | } 33 | 34 | } 35 | ``` 36 | 37 | Now we can submit some data to the app via `POST /article` service endpoint. 38 | Suppose the data I've submitted is: 39 | 40 | ```json 41 | { 42 | "title": "How to control JSON view in Actframework", 43 | "content": "BlahBlah", 44 | "author": "Gelin Luo", 45 | "language": "Java", 46 | "framework": "Actframework", 47 | "tags" : [ 48 | {"name": "java"}, 49 | {"name": "mvc"}, 50 | {"name": "json"} 51 | ] 52 | } 53 | ``` 54 | 55 | I will get the response looks like: 56 | 57 | ```json 58 | { 59 | "id": "58a6409ab6c6fe2138b67f10", 60 | "_created": "17/02/2017 11:15:22 AM", 61 | "content": "BlahBlah", 62 | "v": 1, 63 | "language": "Java", 64 | "author": "Gelin Luo", 65 | "title": "How to control JSON view in Actframework", 66 | "_modified": "17/02/2017 11:15:22 AM", 67 | "framework": "Actframework", 68 | "tags": [ 69 | { 70 | "name": "java" 71 | }, 72 | { 73 | "name": "mvc" 74 | }, 75 | { 76 | "name": "json" 77 | } 78 | ] 79 | } 80 | ``` 81 | 82 | Then if I send `GET /article` request, the `Article.Service.list()` method will response 83 | and return all articles in a list: 84 | 85 | ```json 86 | [ 87 | { 88 | "id": "58a6409ab6c6fe2138b67f10", 89 | "_created": "17/02/2017 11:15:22 AM", 90 | "content": "BlahBlah", 91 | "v": 1, 92 | "language": "Java", 93 | "author": "Gelin Luo", 94 | "title": "How to control JSON view in Actframework", 95 | "_modified": "17/02/2017 11:15:22 AM", 96 | "framework": "Actframework", 97 | "tags": [ 98 | { 99 | "name": "java" 100 | }, 101 | { 102 | "name": "mvc" 103 | }, 104 | { 105 | "name": "json" 106 | } 107 | ] 108 | } 109 | ] 110 | ``` 111 | 112 | Now suppose I want to control the data in the list so that each item returns `author` and `title`, 113 | what I need to do is to add `@PropertySpec` annotation to the `Article.Service.list()` method: 114 | 115 | ```java 116 | @GetAction 117 | @act.util.PropertySpec("author,title") 118 | public Iterable
list() { 119 | return findAll(); 120 | } 121 | ``` 122 | 123 | After making the change and back to PostMan submit the `GET /article` request again, the 124 | response changed as expected: 125 | 126 | ```json 127 | [ 128 | { 129 | "author": "Gelin Luo", 130 | "title": "How to control JSON view in Actframework" 131 | } 132 | ] 133 | ``` 134 | 135 | I can do the same thing in `Article.Service.show(String)` method to define the fields 136 | I want to present in the JSON response. However one thing interesting is what if we 137 | want to make it be flexible so that we allow the front end app to control the fields 138 | to be displayed. This is doable in Act and you just need to make a little bit change 139 | in the `show(String)` method: 140 | 141 | Original code: 142 | 143 | ```java 144 | @GetAction("{id}") 145 | public Article show(String id) { 146 | return findById(id); 147 | } 148 | ``` 149 | 150 | New code: 151 | 152 | ```java 153 | @GetAction("{id}") 154 | public Article show(String id, String fields) { 155 | PropertySpec.current.set(fields); 156 | return findById(id); 157 | } 158 | ``` 159 | 160 | And then we can add `fields` request parameter in our `GET` request, e.g: 161 | 162 | ``` 163 | GET /article/58a6409ab6c6fe2138b67f10?fields=-tags,-content,-_created 164 | ``` 165 | 166 | The above request asking to take `tags`, `content` and `_created` fields out of the JSON response 167 | 168 | The response returned will be: 169 | 170 | ```json 171 | { 172 | "id": "58a6409ab6c6fe2138b67f10", 173 | "v": 1, 174 | "language": "Java", 175 | "author": "Gelin Luo", 176 | "title": "How to control JSON view in Actframework", 177 | "_modified": "17/02/2017 11:15:22 AM", 178 | "framework": "Actframework" 179 | } 180 | ``` 181 | 182 | The source code of this blog is hosted on: 183 | 184 | https://git.oschina.net/greenlaw110/blog_json_view_control 185 | -------------------------------------------------------------------------------- /cn/recipe/di-inject-type.md: -------------------------------------------------------------------------------- 1 | # Actframework依赖注入 II - 注入对象类型 2 | 3 | 本篇讲述Actframework依赖注入的对象类型 4 | 5 | ## 1. 框架内置绑定 6 | 7 | 在ActFramework中有大量的服务和组件都可以直接使用依赖注入,其中包括 8 | 9 | * `ActionContext` - Encapsulate all data/info relevant to an HTTP request context 10 | * `H.Session` - HTTP request session. Also available via `actionContext.session()` 11 | * `H.Flash` - HTTP request flash. Also available via `actionContext.flash()` 12 | * `H.Request` - HTTP request. Also available via `actionContext.req()` 13 | * `H.Response` - HTTP response. Also available via `actionContext.resp()` 14 | * `CliContext` - Encapsulate all data/facilities relevant to a CLI session 15 | * `CliSession` (Since act-0.6.0) - CLI session 16 | * `MailerContext` - Mailer method context 17 | * `ActContext` - A generic `ActContext` depends on the current computation environment, could be either `ActionContext`, `CliContext` or `MailerContext` or `null` 18 | * `Logger` - The `Act.LOGGER` instance 19 | * `UserAgent` - The user agent if in a request handling context 20 | * `AppConfig` - The application configuration object 21 | * `AppCrypto` - The application crypto object 22 | * `CacheService` - The `App.cache()` cache service 23 | * `EventBus` - The application's event bus 24 | * `Locale` - Could be `ActContext.locale()` or `AppConfig.locale()` if there is no context 25 | 26 | ### 1.1 应用服务组件 27 | 28 | * `DbServiceManager` 29 | * `MailerService` 30 | * `Router` 31 | * `CliDispatcher` 32 | * `AppJobManager` 33 | 34 | ## 2. Dao 35 | 36 | 目前支持`EbeanDao`和`MorphiaDao`两种分别用于访问SQL和MongoDB数据库 37 | 38 | ```java 39 | // Demonstrate inject to field 40 | @Controller("user") 41 | public class UserService { 42 | 43 | @javax.inject.Inject 44 | private MorphiaDao userDao; 45 | 46 | @PostAction 47 | public void create(User user) { 48 | userDao.save(user); 49 | } 50 | 51 | } 52 | ``` 53 | 54 | ```java 55 | // Demonstrate inject to parameter 56 | @Controller("user") 57 | public class UserService { 58 | 59 | @PostAction 60 | public void create(User user, MorphiaDao userDao) { 61 | userDao.save(user); 62 | } 63 | 64 | } 65 | ``` 66 | 67 | 如果应用有自定义的Dao,可以直接注入: 68 | 69 | ```java 70 | // The Model 71 | @Entity("user") 72 | public class User extends MorphiaModel { 73 | 74 | public String email; 75 | ... 76 | 77 | public static class Dao extends MorphiaDao { 78 | public User findByEmail(String email) { 79 | return findOneBy("email", email); 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | ```java 86 | // The controller 87 | @Controller("user") 88 | public class UserService { 89 | 90 | @javax.inject.Inject 91 | private User.Dao userDao; 92 | 93 | @GetAction("{email}") 94 | public User findByEmail(String email) { 95 | return userDao.findByEmail(email); 96 | } 97 | 98 | } 99 | ``` 100 | 101 | 102 | 103 | ## 3. 可构造对象 104 | 105 | 任何拥有public缺省构造函数或者带有`@Inject`构造函数的类均可被注入, 例如: 106 | 107 | ```java 108 | // A class with public default constructor 109 | public class Foo { 110 | public Foo() {...} 111 | } 112 | ``` 113 | 114 | ```java 115 | // A class with Inject constructor 116 | public class Bar { 117 | @javax.inject.Inject 118 | public Bar(Foo foo) {...} 119 | } 120 | ``` 121 | 122 | 上面的`Foo`和`Bar`都可以用于依赖注入: 123 | 124 | ```java 125 | public class XxxController { 126 | @Inject Foo foo; 127 | @Inject Bar bar; 128 | 129 | ... 130 | } 131 | ``` 132 | 133 | **注意** 可构造对象不能直接用于参数注入 134 | 135 | ```java 136 | public class XxxController { 137 | 138 | // foo and bar won't be able to injected throght DI 139 | // instead they will be deserialized from form parameters 140 | @PostAction("/xxx") 141 | public void xxxAction(Foo foo, Bar bar) { 142 | } 143 | } 144 | ``` 145 | 146 | 但是可以通过`@Provided`注解来指定使用依赖注入 147 | 148 | ```java 149 | public class YyyController { 150 | 151 | // this time foo and bar will be injected through DI 152 | @PostAction("/yyy") 153 | public void xxxAction(@Provided Foo foo, @Provided Bar bar) { 154 | } 155 | } 156 | ``` 157 | 158 | ## 4. 应用自定义的绑定 159 | 160 | 假设应用自己定义了接口或抽象类, 并且定义了绑定, 可以直接使用依赖注入 161 | 162 | ```java 163 | // The interface 164 | public interface MyService { 165 | void service(); 166 | } 167 | ``` 168 | 169 | ```java 170 | // The implemention one 171 | public class OneService implements MyService { 172 | public void service() {Act.LOGGER.info("ONE is servicing");} 173 | } 174 | ``` 175 | 176 | ```java 177 | // The implemention two 178 | public class TwoService implements MyService { 179 | public void service() {Act.LOGGER.info("TWO is servicing");} 180 | } 181 | ``` 182 | 183 | ```java 184 | // Define bindings 185 | public class MyModule extends org.osgl.inject.Module { 186 | protected void configure() { 187 | bind(MyService.class).to(OneService.class); 188 | bind(MyService.class).named("two").to(TwoService.class); 189 | } 190 | } 191 | ``` 192 | 193 | ```java 194 | // Inject the service 195 | public class Serviced { 196 | @Inject 197 | private MyService one; 198 | 199 | @Inject 200 | @Named("two") 201 | private MyService two; 202 | } 203 | ``` 204 | 205 | ## 链接 206 | 207 | * [ActFramework文档主页](/doc/index.md) 208 | -------------------------------------------------------------------------------- /cn/websocket.md: -------------------------------------------------------------------------------- 1 | # WebSocket 支持 2 | 3 | Websocket 是一种在 HTTP 协议之上的一种技术,可以让浏览器与后台 Web 服务器之间进行双向通讯。ActFramework 自 R1.4.0 开始提供对 WebSocket 的支持 4 | 5 | ## 介绍 I - 一个简单的聊天服务 6 | 7 | 这个简单的[聊天服务应用](https://github.com/actframework/act-demo-apps/edit/master/chatroom) 展示了如何使用 ActFramework 实现一个群聊服务: 8 | 9 | ```java 10 | public class ChatApp { 11 | 12 | @GetAction 13 | public void home() { 14 | } 15 | 16 | @WsAction("msg") 17 | public void onMessage(String message, WebSocketContext context) { 18 | // suppress blank lines 19 | if (S.notBlank(message)) { 20 | context.sendToPeers(message); 21 | } 22 | } 23 | 24 | public static void main(String[] args) throws Exception { 25 | Act.start("chat room"); 26 | } 27 | } 28 | ``` 29 | 30 | 前端代码大致是: 31 | 32 | ```html 33 | 56 | ``` 57 | 58 | ## 介绍 II - 一个 Echo 服务 59 | 60 | 使用 ActFramework 提供的 `WebSocketContext.sendToSelf(String)` API 来实现一个 Echo 服务: 61 | 62 | ```java 63 | @WsAction("echo") 64 | public void onMessage(String message, WebSocketContext context) { 65 | context.sendToSelf(message); 66 | } 67 | ``` 68 | 69 | ## 处理上传消息 70 | 71 | 就像普通的 HTTP 消息响应器使用 `@GetAction`, `@PostAction` 等, 我们使用 `@WsAction` 注解来标识一个 WebSocket 的消息服务端点: 72 | 73 | ```java 74 | @WsAction("/ws/msg") 75 | public void handleMessage(String messageText, WebSocketContext context) { 76 | context.sendToPeers(messageText); 77 | } 78 | ``` 79 | 80 | 上面的代码定义了一个 WebSocket 消息处理器方法 `handleMessage`, 其中有两个参数: 81 | 82 | * `String messsageText` - 任何通过 websocket 连接发送的文字消息 83 | * `WebSocketContext context` - 框架注入的 `WebSocketContext` 对象 84 | 85 | 该方法使用 `WebSocketContext::sendToPeers(String)` API 向所有连接到 `/ws/msg` 的 websocket 发送消息。很明显这是一个聊天室应用。 86 | 87 | ## 绑定复杂类型 88 | 89 | 浏览器可以向服务器发送 JSON 编码的复杂类型,比如: 90 | 91 | ```javascript 92 | socket.send(JSON.stringify({room: '@room', text: msg, from: me.id, nickname: me.nickname})); 93 | ``` 94 | 95 | 在服务器端可以定义一个 `Message` 类: 96 | 97 | ```java 98 | @Data 99 | public class Message implements SimpleBean { 100 | 101 | public String text; 102 | 103 | public String room; 104 | 105 | public String from; 106 | 107 | public String nickname; 108 | 109 | } 110 | ``` 111 | 112 | 然后我们可以直接在消息响应器中使用 `Message` 类作为参数: 113 | 114 | ```java 115 | @WsAction("/chat") 116 | public void handlePojoMessage(Message pojo, WebSocketContext context) { 117 | context.sendJsonToTagged(pojo, pojo.room); 118 | } 119 | ``` 120 | 121 | 上例中我们使用 `WebSocketContext::sendJsonToTagged(Object msg, String tag)` 实现了一个简单的多聊天室应用. 收到的消息发送给所有加了 `message.room` 标签的 websocket 连接。下一节我们会介绍如何给一个 websocket 连接打上标签: 122 | 123 | ## 处理连接建立和断开事件 124 | 125 | 当浏览器中执行 `new websocket('ws://myhost/myurl')` 语句的时候,将发出一个 HTTP GET 请求到 `/myurl`,undertow 会升级 HTTP 协议并建立一个 websocket 连接. ActFramework 通过事件分发机制支持应用设置连接建立时的逻辑: 126 | 127 | ```java 128 | private static final AtomicInteger CONN_COUNTER = new AtomicInteger(0); 129 | 130 | @OnEvent 131 | public static void handleConnection(WebSocketConnectEvent event) { 132 | CONN_COUNTER.incrementAndGet(); 133 | final WebSocketContext context = event.source(); 134 | context.tag(Room.MAIN); 135 | } 136 | ``` 137 | 138 | 上面的事件响应代码会在任何一个 websocket 连接建立时触发。应用会增加连接计数器,然后通过 `WebSocketContext.tag(String label)` API 将新连接打上 `main room` 的标签。这样所有发送到 `main room` 的消息会被分发到新的连接。按照我们上面的代码,客户端发送到 `main room` 的消息可以是: 139 | 140 | ```json 141 | { 142 | "text": "Hi", 143 | "room": "main", 144 | "from": "tom@abc.com", 145 | "nickname: "Tommy" 146 | } 147 | ``` 148 | 149 | 开发人员也可以针对连接断开事件编码: 150 | 151 | ```java 152 | public static void handleConnectionClose(WebSocketCloseEvent event) { 153 | final WebSocketContext context = event.source(); 154 | CONN_COUNTER.decrementAndGet(); 155 | } 156 | ``` 157 | 158 | **注意** 任何 websocket 连接在建立或者断开的时候都会触发相应的 `WebSocketConnectEvent` 和 `WebSocketCloseEvent` 事件,而和具体的 websocket 服务 URL 无关。假如应用有多个 websocket 服务端点, 需要处理特定 URL 连接建立断开事件, 应用必须检查连接的 URL: 159 | 160 | ```java 161 | public static void handleConnectionClose(WebSocketCloseEvent event) { 162 | final WebSocketContext context = event.source(); 163 | if ("/ws/endpoint1".equals(context.url())) { 164 | System.out.println("endpoint1 closed"); 165 | } 166 | } 167 | ``` 168 | 169 | ## 发送消息到特定用户 170 | 171 | 如果应用实现了用户认证,且认证用户的用户名按照 `AppConfig.sessionKeyUsername()` 定义的 `key` 保存在 `H.Session` 中,ActFramework 提供了非常方便的 API 来将消息发送给特定用户: 172 | 173 | ```java 174 | @OnEvent 175 | public static void handleNewsUpdate(NewsUpdateEvent event, User.Dao userDao, WebSocketConnectionManager connectionManager) { 176 | NewsUpdate update = event.source(); 177 | List users = userDao.findBySubscription(update.topic()); 178 | for (User user: users) { 179 | connectionManager.sendJsonToUser(update, user.username()); 180 | } 181 | } 182 | ``` 183 | 184 | -------------------------------------------------------------------------------- /en/morphia.md: -------------------------------------------------------------------------------- 1 | # Access MongoDB with Morphia 2 | 3 | ## Setup 4 | 5 | In order to use Morphia you need to add the following dependencies in your `pom.xml` file: 6 | 7 | ```xml 8 | 9 | org.actframework 10 | act-morphia 11 | 0.1.1-SNAPSHOT 12 | 13 | ``` 14 | 15 | ## Configuration 16 | 17 | A very simple mongodb configuration: 18 | 19 | ``` 20 | # If you have only one DBPlugin in your class path, then 21 | # you do not need to specify the db.impl configuration 22 | db.impl=act.db.morphia.MorphiaPlugin 23 | db.uri=mongodb://localhost/mydb 24 | ``` 25 | 26 | **Tips** You don't even need that configuration. ActFramework will put all your mongodb data into the `test` database 27 | 28 | A little bit more sophisticated configuration: 29 | 30 | ``` 31 | db.url=mongodb://:@:,:,...,hostN:portN/dbname?replicaSet=...&connectTimeoutMS=... 32 | ``` 33 | 34 | 35 | ## Model 36 | 37 | Let's create a simple Product model with two properties: 38 | 39 | 1. `name` 40 | 1. `price` 41 | 42 | ```java 43 | package com.mycom.myprj; 44 | 45 | import org.mongodb.morphia.annotations.Entity; 46 | import act.db.morphia.MorphiaModel; 47 | 48 | @Entity("prod") 49 | public class Product extends MorphiaModel { 50 | private String name; 51 | private int price; 52 | 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | public void setName(String name) { 58 | this.name = name; 59 | } 60 | 61 | public int getPrice() { 62 | return price; 63 | } 64 | 65 | public void setPrice(int price) { 66 | this.price = price; 67 | } 68 | } 69 | ``` 70 | 71 | ## DAO and CRUD 72 | 73 | Now that the model has been defined, let's take a look at how to use built-in DAO to do CRUD operations. 74 | 75 | ```java 76 | package com.mycom.myprj; 77 | 78 | import act.controller.Controller; 79 | import act.db.morphia.MorphiaDao; 80 | import org.bson.types.ObjectId; 81 | import org.osgl.mvc.annotation.GetAction; 82 | import org.osgl.mvc.annotation.PostAction; 83 | import org.osgl.mvc.annotation.PutAction; 84 | 85 | 86 | @Controller("/prod") 87 | public class ProductController extends Controller.Util { 88 | 89 | private MorphiaDao dao = Product.dao(); 90 | 91 | @GetAction 92 | public Iterable list() { 93 | return dao.findAll(); 94 | } 95 | 96 | @GetAction("/{id}") 97 | public Product show(String id) { 98 | return dao.findById(new ObjectId(id)); 99 | } 100 | 101 | @PostAction 102 | public void create(Product product) { 103 | dao.save(product); 104 | } 105 | 106 | @PutAction("/{id}/name") 107 | public void update(String id, String name) { 108 | Product product = dao.findById(new ObjectId(id)); 109 | notFoundIfNull(product); 110 | product.setName(name); 111 | dao.save(product); 112 | } 113 | 114 | @DeleteAction 115 | public void delete(String id) { 116 | dao.deleteById(new ObjectId(id)); 117 | } 118 | } 119 | ``` 120 | 121 | ## Search 122 | 123 | Act provide a set of search methods in `Dao` interface: 124 | 125 | ```java 126 | // find by name 127 | Iterable products = dao.findBy("name", name); 128 | 129 | // find all that price is less than 10000 130 | Iterable products = dao.findBy("price <", 100000); 131 | 132 | // find by name and price 133 | Iterable products = dao.findBy("name, price <", name, 100000); 134 | 135 | // find by name using regular expression 136 | Iterable products = dao.findBy("name", Pattern.compile("laptop")); 137 | ``` 138 | 139 | ## Extend `MorphiaDao` 140 | 141 | Sometime it is good to extend the `MorphiaDao` class and create a dedicated DAO class for a certain type to build some domain logic, in which case you extend's the DAO's concept to business service. 142 | 143 | ```java 144 | @Entity("prod") 145 | public class Product extends MorphiaModel { 146 | private String name; 147 | private int price; 148 | 149 | public String getName() { 150 | return name; 151 | } 152 | 153 | public void setName(String name) { 154 | this.name = name; 155 | } 156 | 157 | public int getPrice() { 158 | return price; 159 | } 160 | 161 | public void setPrice(int price) { 162 | this.price = price; 163 | } 164 | 165 | public static class Dao extends MorphiaDao { 166 | 167 | public static final int LOW_PRICE = 10000; 168 | public static final int HIGH_PRICE = 999900; 169 | 170 | public Dao() { 171 | super(Product.class); 172 | } 173 | 174 | public Iterable findLowPriceProducts() { 175 | return findBy("price <", LOW_PRICE); 176 | } 177 | 178 | public Iterable findHighPriceProducts() { 179 | return findBy("price >", HIGH_PRICE); 180 | } 181 | } 182 | } 183 | 184 | ``` 185 | 186 | ## Using extended DAO class 187 | 188 | Once you have extended the DAO, you can use it following the same way as shown above, just change the type as shown below: 189 | 190 | ```java 191 | //private MorphiaDao dao = Product.dao(); 192 | private Product.Dao dao = Product.dao(); 193 | ``` 194 | 195 | So ActFramework detects your implementation of the DAO and will use that class instead of the standard one. 196 | 197 | [Back to index](index.md) -------------------------------------------------------------------------------- /en/recipe/di-inject-type.md: -------------------------------------------------------------------------------- 1 | # Dependency Injection in Actframework II - Inject object type 2 | 3 | This recipe talks about Inject object type in ActFramework 4 | 5 | ## 1. Built-in bindings 6 | 7 | There are many services and components in Actframework can be injected directly, including: 8 | 9 | * `ActionContext` - Encapsulate all data/info relevant to an HTTP request context 10 | * `H.Session` - HTTP request session. Also available via `actionContext.session()` 11 | * `H.Flash` - HTTP request flash. Also available via `actionContext.flash()` 12 | * `H.Request` - HTTP request. Also available via `actionContext.req()` 13 | * `H.Response` - HTTP response. Also available via `actionContext.resp()` 14 | * `CliContext` - Encapsulate all data/facilities relevant to a CLI session 15 | * `CliSession` (Since act-0.6.0) - CLI session 16 | * `MailerContext` - Mailer method context 17 | * `ActContext` - A generic `ActContext` depends on the current computation environment, could be either `ActionContext`, `CliContext` or `MailerContext` or `null` 18 | * `Logger` - The `Act.LOGGER` instance 19 | * `UserAgent` - The user agent if in a request handling context 20 | * `AppConfig` - The application configuration object 21 | * `AppCrypto` - The application crypto object 22 | * `CacheService` - The `App.cache()` cache service 23 | * `EventBus` - The application's event bus 24 | * `Locale` - Could be `ActContext.locale()` or `AppConfig.locale()` if there is no context 25 | 26 | ### 1.1 App services 27 | 28 | * `DbServiceManager` 29 | * `MailerService` 30 | * `Router` 31 | * `CliDispatcher` 32 | * `AppJobManager` 33 | 34 | ## 2. Dao 35 | 36 | At the moment ActFramework support `EbeanDao` and `MorphiaDao` for SQL database MongoDB access respectively 37 | 38 | ```java 39 | // Demonstrate inject to field 40 | @Controller("user") 41 | public class UserService { 42 | 43 | @javax.inject.Inject 44 | private MorphiaDao userDao; 45 | 46 | @PostAction 47 | public void create(User user) { 48 | userDao.save(user); 49 | } 50 | 51 | } 52 | ``` 53 | 54 | ```java 55 | // Demonstrate inject to parameter 56 | @Controller("user") 57 | public class UserService { 58 | 59 | @PostAction 60 | public void create(User user, MorphiaDao userDao) { 61 | userDao.save(user); 62 | } 63 | 64 | } 65 | ``` 66 | 67 | If application has defined its own Dao, it can be injected also 68 | 69 | ```java 70 | // The Model 71 | @Entity("user") 72 | public class User extends MorphiaModel { 73 | 74 | public String email; 75 | ... 76 | 77 | public static class Dao extends MorphiaDao { 78 | public User findByEmail(String email) { 79 | return findOneBy("email", email); 80 | } 81 | } 82 | } 83 | ``` 84 | 85 | ```java 86 | // The controller 87 | @Controller("user") 88 | public class UserService { 89 | 90 | @javax.inject.Inject 91 | private User.Dao userDao; 92 | 93 | @GetAction("{email}") 94 | public User findByEmail(String email) { 95 | return userDao.findByEmail(email); 96 | } 97 | 98 | } 99 | ``` 100 | 101 | 102 | 103 | ## 3. Class that can be constructed (without external parameter) 104 | 105 | Any class with public default constructor or constructor with `@Inject` annotation can be injected 106 | 107 | ```java 108 | // A class with public default constructor 109 | public class Foo { 110 | public Foo() {...} 111 | } 112 | ``` 113 | 114 | ```java 115 | // A class with Inject constructor 116 | public class Bar { 117 | @javax.inject.Inject 118 | public Bar(Foo foo) {...} 119 | } 120 | ``` 121 | 122 | The `Foo` and `Bar` depicted above can be injected 123 | 124 | ```java 125 | public class XxxController { 126 | @Inject Foo foo; 127 | @Inject Bar bar; 128 | 129 | ... 130 | } 131 | ``` 132 | 133 | **Note** The constructable class cannot be injected into method parameter list directly 134 | 135 | ```java 136 | public class XxxController { 137 | 138 | // foo and bar won't be able to injected throght DI 139 | // instead they will be deserialized from form parameters 140 | @PostAction("/xxx") 141 | public void xxxAction(Foo foo, Bar bar) { 142 | } 143 | } 144 | ``` 145 | 146 | However it can request framework to inject constructable object throug `@Provided` annotation 147 | 148 | ```java 149 | public class YyyController { 150 | 151 | // this time foo and bar will be injected through DI 152 | @PostAction("/yyy") 153 | public void xxxAction(@Provided Foo foo, @Provided Bar bar) { 154 | } 155 | } 156 | ``` 157 | 158 | ## 4. Application defined binding 159 | 160 | If application defined an interface or abstract class and the provider binding, the interface/class can be injected: 161 | 162 | ```java 163 | // The interface 164 | public interface MyService { 165 | void service(); 166 | } 167 | ``` 168 | 169 | ```java 170 | // The implemention one 171 | public class OneService implements MyService { 172 | public void service() {Act.LOGGER.info("ONE is servicing");} 173 | } 174 | ``` 175 | 176 | ```java 177 | // The implemention two 178 | public class TwoService implements MyService { 179 | public void service() {Act.LOGGER.info("TWO is servicing");} 180 | } 181 | ``` 182 | 183 | ```java 184 | // Define bindings 185 | public class MyModule extends org.osgl.inject.Module { 186 | protected void configure() { 187 | bind(MyService.class).to(OneService.class); 188 | bind(MyService.class).named("two").to(TwoService.class); 189 | } 190 | } 191 | ``` 192 | 193 | ```java 194 | // Inject the service 195 | public class Serviced { 196 | @Inject 197 | private MyService one; 198 | 199 | @Inject 200 | @Named("two") 201 | private MyService two; 202 | } 203 | ``` 204 | 205 | ## Links 206 | 207 | * [ActFramework document](/doc/index.md) 208 | -------------------------------------------------------------------------------- /cn/interceptor.md: -------------------------------------------------------------------------------- 1 | # 拦截器 2 | 3 | ActFramework应用程序可以使用两种方式创建拦截器: 4 | 5 | 1. 基于注解的方式 (类似PlayFramework) 6 | 1. 通过继承写拦截器类 7 | 8 | ## 基于注解的拦截器 9 | 10 | 该设计完全基于PlayFramework 1.x的方式 11 | 12 | ### Before 13 | 14 | 在某方法上使用`@Before`注解告诉ActFramework在执行该类中的所有请求响应器之前先调用该方法 15 | 16 | 下面我们使用拦截器来创建一个安全检查: 17 | 18 | ```java 19 | public class Admin extends Controller.Util { 20 | 21 | @Before 22 | public void checkAuthentification() { 23 | if(session.get("user") == null) { 24 | redirect("/login"); 25 | } 26 | } 27 | 28 | public void index() { 29 | Iterable users = userDao.findAll(); 30 | render(users); 31 | } 32 | 33 | … 34 | } 35 | ``` 36 | 37 | 如果不需要拦截器拦截发送给某些响应器的请求,可以使用`except`参数来指定豁免清单: 38 | 39 | ```java 40 | public class Admin extends Controller.Util { 41 | 42 | @Before(except="login") 43 | static void checkAuthentification() { 44 | if(session.get("user") == null) { 45 | redirect("/login"); 46 | } 47 | } 48 | 49 | public void index() { 50 | Iterable users = userDao.findAll(); 51 | render(users); 52 | } 53 | 54 | … 55 | } 56 | ``` 57 | 58 | 另一方面,你也可以使用`only`参数来指定该拦截器唯一作用于的响应器: 59 | 60 | ```java 61 | public class Admin extends Controller.Util { 62 | 63 | @Before(only={"login","logout"}) 64 | public void doSomething() { 65 | … 66 | } 67 | … 68 | } 69 | ``` 70 | 71 | 除了`@Before`, `except`和`only`参数也可以在`@After`, `@Catch`和`@Finally`注解中使用. 72 | 73 | ### @After 74 | 75 | 标注有`@After`的方法在执行本类中所有的响应器之后被调用. 76 | 77 | ```java 78 | public class Admin extends Controller.Util { 79 | 80 | @After 81 | public void log() { 82 | Logger.info("Action executed ..."); 83 | } 84 | 85 | public void index() { 86 | Iterable users = userDao.findAll(); 87 | render(users); 88 | } 89 | 90 | … 91 | } 92 | ``` 93 | 94 | ### @Catch 95 | 96 | 如果某方法标注有`@Catch`,该方法在当前类的响应器抛出异常后被调用. 被抛出的异常作为参数传递给`@Catch`方法. 97 | 98 | ```java 99 | public class Admin extends Controller.Util { 100 | 101 | @Catch(IllegalStateException.class) 102 | public void logIllegalState(Throwable throwable) { 103 | Logger.error("Illegal state %s…", throwable); 104 | } 105 | 106 | public void index() { 107 | List users = userDao.findAllAsList(); 108 | if (users.size() == 0) { 109 | throw new IllegalStateException("Invalid database - 0 users"); 110 | } 111 | render(users); 112 | } 113 | } 114 | ``` 115 | 116 | 和通常的Java异常处理类似,你可以在`@Catch`拦截器方法中申明父类异常来捕获子类异常 117 | 118 | ```java 119 | public class Admin extends Controller.Util { 120 | 121 | @Catch(value = Throwable.class, priority = 1) 122 | public void logThrowable(Throwable throwable) { 123 | // Custom error logging… 124 | Logger.error("EXCEPTION %s", throwable); 125 | } 126 | 127 | @Catch(value = IllegalStateException.class, priority = 2) 128 | public void logIllegalState(Throwable throwable) { 129 | Logger.error("Illegal state %s…", throwable); 130 | } 131 | 132 | public void index() { 133 | List users = userDao.findAllAsList(); 134 | if(users.size() == 0) { 135 | throw new IllegalStateException("Invalid database - 0 users"); 136 | } 137 | render(users); 138 | } 139 | } 140 | ``` 141 | 142 | ### @Finally 143 | 144 | 标注有`@Finally`注解的方法在当前控制器的响应方法执行完成之后被调用,即便响应方法抛出异常,该拦截器也会被调用 145 | 146 | ```java 147 | public class Admin extends Controller.Util { 148 | 149 | @Finally 150 | static void log() { 151 | Logger.info("Response contains : " + response.out); 152 | } 153 | 154 | public static void index() { 155 | List users = userDao.findAllAsList(); 156 | render(users); 157 | } 158 | … 159 | } 160 | ``` 161 | 162 | ### 类继承对拦截器的影响 163 | 164 | 如果控制器继承了某个基类,在基类中定义的拦截器也适用于子类控制器 165 | 166 | ### 使用@With注解来重用拦截器定义 167 | 168 | 如果你的控制器已经继承了某个基类,而你需要重用定义在另一个类的拦截器,可以通过`@With`注解来实现: 169 | 170 | 171 | 某个定义了拦截器的类: 172 | 173 | ```java 174 | public class Secure extends Controller.Util { 175 | 176 | @Before 177 | static void checkAuthenticated() { 178 | if(!session.containsKey("user")) { 179 | unAuthorized(); 180 | } 181 | } 182 | } 183 | ``` 184 | 185 | 控制器类: 186 | 187 | ```java 188 | @With(Secure.class) 189 | public class Admin extends MyOtherBaseClass { 190 | 191 | … 192 | } 193 | ``` 194 | 195 | ## 实现拦截器接口 196 | 197 | 基于注解的拦截器非常轻量,不过只适用于定义或这引用了拦截器的控制器。如果需要实现全局拦截,可以通过继承XxxInterceptor来实现 198 | 199 | ```java 200 | import act.app.ActionContext; 201 | import act.handler.builtin.controller.BeforeInterceptor; 202 | import act.plugin.Plugin; 203 | import org.osgl.http.H; 204 | import org.osgl.mvc.result.Result; 205 | 206 | import javax.inject.Singleton; 207 | 208 | @Singleton 209 | public class MockRequestContentAcceptor extends BeforeInterceptor { 210 | 211 | public MockRequestContentAcceptor() { 212 | super(1); 213 | Plugin.InfoRepo.register(this); 214 | } 215 | 216 | @Override 217 | public Result handle(ActionContext actionContext) throws Exception { 218 | String s = actionContext.paramVal("fmt"); 219 | if ("json".equalsIgnoreCase(s)) { 220 | actionContext.accept(H.Format.JSON); 221 | } else if ("csv".equalsIgnoreCase(s)) { 222 | actionContext.accept(H.Format.CSV); 223 | } else if ("xml".equalsIgnoreCase(s)) { 224 | actionContext.accept(H.Format.XML); 225 | } 226 | return null; 227 | } 228 | } 229 | 230 | ``` 231 | 232 | 上面的代码拦截所有的请求,检查是否有`fmt`参数,如果发现`fmt`参数则设置相应的`Accept`头。 233 | 234 | 类似的拦截器接口还有: 235 | 236 | 1. `act.handler.builtin.controller.AfterInterceptor` 237 | 1. `act.handler.builtin.controller.ExceptionInterceptor` 238 | 1. `act.handler.builtin.controller.FinallyInterceptor` 239 | 240 | [返回目录](index.md) -------------------------------------------------------------------------------- /cn/releases/r1.2.0.md: -------------------------------------------------------------------------------- 1 | # 1.2.0版的新特性 2 | 3 | 4 | #### #136 `@With` 注解现在可以应用于方法上了 5 | 6 | 在 r1.2.0 之前 `@With` 注解用来将拦截器应用于某个控制器上,例如: 7 | 8 | ```java 9 | public class MyInterceptor extends Controller.Util { 10 | @Before 11 | public void logRequest(H.Request req) { 12 | Act.LOGGER.trace("<<< req to %s", req.fullUrl()); 13 | } 14 | 15 | @After 16 | public void logRequestDone(H.Request req) { 17 | Act.LOGGER.trace(">>> req to %s", req.fullUrl()); 18 | } 19 | } 20 | ``` 21 | 22 | ```java 23 | @With(MyInterceptor.class) 24 | public class MyController { 25 | ... 26 | } 27 | ``` 28 | 29 | 上述代码表示拦截器 `MyInterceptor` 里面所有的拦截方法都将被应用于控制器 `MyController` 里面所有的响应方法上。现在 r1.2.0 允许应用将 `@With` 注解应用于具体的响应方法上,例如: 30 | 31 | ```java 32 | public class MyControllerV2 { 33 | @With(MyInterceptor.class) 34 | @GetAction("/foo") 35 | public void handlerNeedsAuditing() { 36 | ... 37 | } 38 | 39 | @GetAction("/bar") 40 | public void handlerWithoutAuditing() { 41 | ... 42 | } 43 | } 44 | ``` 45 | 46 | 改动之后的 `MyControllerV2` 上拦截器只作用于发送到 `/foo` 的请求,而发送到 `/bar/` 的请求则不会应用拦截器 47 | 48 | 49 | #### #152 允许将拦截器标注为全局有效 50 | 51 | 以前如果你想应用一个拦截器到控制器上,必须在控制器上使用 `@With` 注解来引入拦截器。现在 r1.2.0 允许我们声明某个拦截器类或者方法为全局有效: 52 | 53 | ```java 54 | @Global 55 | public class MyInterceptor extends Controller.Util { 56 | @Before 57 | public void logRequest(H.Request req) { 58 | Act.LOGGER.trace("<<< req to %s", req.fullUrl()); 59 | } 60 | 61 | @After 62 | public void logRequestDone(H.Request req) { 63 | Act.LOGGER.trace(">>> req to %s", req.fullUrl()); 64 | } 65 | } 66 | ``` 67 | 68 | 上面的代码告诉 ActFramework `MyInterceptor` 所有的拦截器方法均全局有效 69 | 70 | 你也可以将 `@Global` 放在某个特定的拦截器方法上面表示该拦截器方法需要全局有效: 71 | 72 | ```java 73 | public class MyInterceptor extends Controller.Util { 74 | @Global 75 | @Before 76 | public void logRequest(H.Request req) { 77 | Act.LOGGER.trace("<<< req to %s", req.fullUrl()); 78 | } 79 | 80 | @After 81 | public void logRequestDone(H.Request req) { 82 | Act.LOGGER.trace(">>> req to %s", req.fullUrl()); 83 | } 84 | } 85 | ``` 86 | 87 | 上面的代码表示只有 `@Before` 拦截器方法才是全局有效的。 88 | 89 | 90 | #### #153 在 @DbBind 的时候使用 @NotNull 注解 91 | 92 | 在 ActFramework 应用里面我们可以使用 `@DbBind` 来绑定某个请求/URL/表单变量到响应方法(或者拦截器方法)参数上,例如: 93 | 94 | ```java 95 | @GetAction("/order/{id}/price") 96 | public double update(@DbBind("id") Order order) { 97 | notFoundIfNull(order); 98 | return order.getPrice(); 99 | } 100 | ``` 101 | 102 | 上述代码中 `notFoundIfNull(order);` 是告诉 ActFramework 在传入的 `id` 找不到对应的 `Order` 的时候返回 `404 Not Found` 响应。如果没有这一句,那当找不到绑定数据的时候你会在 `return order.getPrice();` 这一行得到一个 `NullPointerException`,而触发一个 `500 内部错误` 响应。 103 | 104 | 现在 r1.2.0 我们引入了一种更加简洁的方式来描述上述逻辑: 105 | 106 | ```java 107 | @GetAction("/order/{id}/price") 108 | public double update(@DbBind("id") @NotNull Order order) { 109 | return order.getPrice(); 110 | } 111 | ``` 112 | 113 | 上面的 `NotNull` 是 Java 标准的数据有效性检验框架提供的注解. 114 | 115 | ActFramework 还改进了(开发模式下的)错误页面,这样可以让开发人员非常清晰地看到是什么原因造成的 404 返回: 116 | 117 | **源码** 118 | 119 | ![code](http://i.imgur.com/qV3y89l.png) 120 | 121 | **当 ID 不正确时的错误页面** 122 | 123 | ![error](http://i.imgur.com/RK3dPwx.png) 124 | 125 | 126 | #### #157 路由支持 SEO 127 | 128 | 现在我们可能在一些网站上发现针对搜索引擎优惠的 URL, 比如下面两个 URL 打开的页面是一样的: 129 | 130 | * [`http://stackoverflow.com/questions/43406011/actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found`](http://stackoverflow.com/questions/43406011/actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found) 131 | * [`http://stackoverflow.com/questions/43406011`](http://stackoverflow.com/questions/43406011) 132 | 133 | 但是前者的 URL 带的 `actframework-run-error-org-osgl-exception-unexpectedexception-app-not-found` 让 URL 能够被搜索引擎理解并做相应的处理。 134 | ActFramework r1.2.0 提供了一种机制允许应用创建类似的 URL: 135 | 136 | ```java 137 | @GetAction("/article/{id}/...") 138 | public Article getArticle(@DbBind("id") Article article) { 139 | return article; 140 | } 141 | ``` 142 | 143 | 上面代码中的 URL 路径 `/article/{id}/...` 以 `...` 结束, 表示在 `/article/{id}/` 之后的任何字符都将被忽略。这样可以让开发人员构造针对 SEO 优化的 URL 144 | 145 | 146 | #### #160 在 `Controller.Base` 中植入 `ActionContext` 字段 147 | 148 | `Controller.Util` 类提供了丰富的工具方法来帮助应用程序,因此应用程序的控制器类通常会从 `Controller.Util` 类继承下来。另一方面应用程序经常会在响应方法中使用到 `ActionContext` 对象,而获得该对象的办法通常有两种,一种声明一个依赖注入字段,二是在响应方法的参数列表里面声明该对象。 149 | 150 | 现在 r1.2.0 我们引入了一个新的控制器基类:`Controller.Base`。该基类和 `Controller.Util` 的区别是前者声明了一个依赖注入字段 `protected ActionContext context;` 这样应用从该基类派生出的控制器类自动拥有了 `context` 字段而无需声明: 151 | 152 | ```java 153 | public class MyController extends Controller.Base { 154 | @GetAction("/foo") 155 | public String foo() { 156 | return context.i18n("foo"); 157 | } 158 | } 159 | ``` 160 | 161 | **注意** 从 `Controller.Base` 派生出的控制器类可以直接使用 `Controller.Util` 所有的方法,因为 `Controller.Base` 继承 了 `Controller.Util` 162 | 163 | 看到这里大家可能会问,为什么不直接在 `Controller.Util` 类里面声明 `protected ActionContext context` 字段呢? 原因在于 `ActionContext context` 字段是有状态的,即每次请求带来的 context 都是不同的. 因此 ActFramework 在响应新请求的时候必须创建控制器的新实例. 而并非所有的控制器都需要一个 `ActionContext` 类型的字段。在这种情况下保持 `Controller.Util` 的无状态性可以让无状态的控制器从其派生而不至于损失单例的资格。 164 | 165 | 166 | #### #161 提供一种机制标注注入字段为无状态的 167 | 168 | ActFramework 的灵动之处体现在很多地方,其中一处是自动检测到没有声明字段的控制器类的时候使用同样的实例来响应不同的请求,这很酷. 不过我们需要做到更进一步,在某些时候我们注入的对象本身是无状态的,比如 169 | 170 | ```java 171 | public class OrderService { 172 | @Inject 173 | private Order.Dao dao; 174 | 175 | ... 176 | } 177 | ``` 178 | 179 | 上面的`OrderService` 控制器注入了一个字段 `Order.Dao dao`, 但是因为 `Order.Dao` 是无状态的(假设如此), 或者说我们注入的每个 `Order.Dao` 都是同行一个实例,在这种情况下,我们没有理由为 `OrderService` 控制器对每个请求创建一个新实例,完全可以将其当作单例处理. r1.2.0版我们提供了两种方式实现上述需求 180 | 181 | 方法一, 在注入的字段上添加 `@Global` 注解: 182 | 183 | ```java 184 | public class OrderService { 185 | @Inject 186 | @Global 187 | private Order.Dao dao; 188 | 189 | ... 190 | } 191 | ``` 192 | 193 | 上面的 `@Global` 注解告诉 ActFramework `dao` 实例是跨所有的 OrderService` 实例存在的, 因此 ActFramework 不需要因为这个字段而将 `OrderService` 控制器考虑为有状态控制器。 194 | 195 | 方法二 如果你能控制注入类,比如这个例子中的 `Order.Dao` 类, 你可以在类上加上 `@Stateless` 注解: 196 | 197 | ```java 198 | @Entity("order") 199 | public class Order { 200 | ... 201 | @Stateless 202 | public static class Dao extends EbeanDao { 203 | ... 204 | } 205 | } 206 | ``` 207 | 208 | 现在你在注入 `OrderDao dao` 字段的时候不需要加上 `@Global` 注解,ActFramework 自动根据 Order.Dao 类上的 `@Stateless` 注解推断出了这个字段的无状态性: 209 | 210 | ```java 211 | public class OrderService { 212 | @Inject 213 | private Order.Dao dao; 214 | 215 | ... 216 | } 217 | ``` 218 | 219 | 即使是以上代码,ActFramework 也将会把 `OrderService` 当作无状态的单例控制器处理 220 | --------------------------------------------------------------------------------