HelloWorld 是一个HowToDoIt 组织的第一个项目, 一个简单的 MVC 展示应用. 实现项目需要响应发送到 GET /
端点的请求并显示一个主页
Hello World
- 其中 World
可以被 who
查询参数的值替代下面是所有参与项目的链接:
public class AppEntry {
/**
* The home (`/`) endpoint.
*
* @param who
* request query parameter to specify the hello target.
* default value is `World`.
*/
@GetAction
public void home(@DefaultValue("World") @Output String who) {}
public static void main(String[] args) throws Exception {
Act.start();
}
}
ActFramework 的实现相当简单, 使用了一个类来启动并响应请求.
ActFramework 使用 Rythm 模板引擎生成主页
<!DOCTYPE html>
<html lang="en">
@args String who
<head>
<title>Hello World - ActFramework</title>
</head>
<body>
<h1>Hello @who</h1>
<div>@act.Act.appVersion().getVersion()</div>
<p>
Powered by ActFramework @act.Act.VERSION.getVersion()
</p>
</body>
</html>
ActFramwork 项目实现了所有的需求, 包括可选项.
值得注意的是 ActFramework 使用了 osgl-version 来管理应用版本, 无需编码即可直接从项目的 pom.xml
定义中拿到版本号, 对应用开发来说非常便利: 直接使用 Act.appVersion()
即可拿到应用定义在 pom.xml
文件的版本号. 作为比较, 获得 ActFramework 框架版本号的方法是访问 Act.VERSION
常量.
ActFramework 实现提供的独特优势: 使用 yaml 文件实现了端到端自动化测试.
Scenario(Hello World):
description: a web page says Hello
interactions:
- description: send request to the app without parameter
request:
method: GET
url: /
response:
html:
h1: Hello World
head title:
- contains: Hello World
- contains: ActFramework
p:
<any>:
- contains: Powered by ActFramework
- description: send request to the app with [who = ActFramework]
request:
method: GET
url: /?who=ActFramework
response:
html:
h1: Hello ActFramework
public class Application {
private static void index(RouteContext ctx) {
String message = "Hello " + ctx.request().query("who", "World");
ctx.attribute("message", message);
ctx.attribute("appVersion", WebContext.blade().env("app.version", "default version"));
ctx.attribute("bladeVersion", Const.VERSION);
ctx.render("index.html");
}
public static void main(String[] args) {
Blade.of()
.get("/", Application::index)
.start(Application.class, args);
}
}
Blade 也使用了一个 Java 源码就实现了所有的特性, 代码非常优雅. Blade 使用了类似 velocity 的模板引擎生成主页:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
<h1>${message}</h1>
Build By ${name} ${version}
</body>
</html>
和 ActFramework 的实现不同, Blade 没有从 pom.xml
文件中获取应用版本信息, 而是在额外的配置文件中定义应用版本.
public class HellWorldController extends Controller {
public void index(@Para(value="who", defaultValue="World")String who){
setAttr("who",who).render("index.html");
}
}
public class ProjectConfig extends JFinalConfig{
public static void main(String[] args) {
JFinal.start("src/main/webapp", 8000, "/");
}
public void configRoute(Routes me) {
me.setBaseViewPath("/WEB-INF/views").add("/", HellWorldController.class);
}
public void configConstant(Constants me) {
}
public void configEngine(Engine me) {
}
public void configPlugin(Plugins me) {
}
public void configInterceptor(Interceptors me) {
}
public void configHandler(Handlers me) {
}
}
JFinal 使用了两个 Java 文件, 其中的 Controller 类非常简洁. 不过额外的 ProjectConfig.java
类让整个项目看上去就稍稍繁复了一点点.
JFinal 使用自己的 Enjoy 模板引擎生成主页:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>HelloWorld</title>
</head>
<body>
<h1>Hello,#(who ?? 'UNKNOWN')!!^_^</h1>
<form action="/">
<fieldset>
<legend>Say Hello To The World</legend>
<div>who: <input type="text" name="who"/></div>
<input type="submit" value="==问好==">
</fieldset>
</body>
</form>
<div style="text-align:center;color:red;padding:10px;">Power By JFinal#(com.jfinal.core.Const::JFINAL_VERSION)</div>
</html>
JFinal 提供了框架版本号,但没有提供应用版本号
@IocBean
public class MainLauncher {
@Inject
protected PropertiesProxy conf;
@At("/")
@Ok("jst:/index.html")
public NutMap index(String who) throws IOException {
NutMap re = new NutMap();
re.put("who", Strings.sBlank(who, "world"));
re.put("self_version", conf.get("app.build.version", "未知"));
re.put("nutz_version", Nutz.version());
return re;
}
public static void main(String[] args) throws Exception {
new NbApp().run();
}
}
Nutz 的实现和 Blade/ActFramwork 一样都是一个文件解决问题. Nutz 也从 pom.xml
文件中获取项目版本号.
Nutz's 用来生成主页的模板代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello ${=obj.who}</title>
</head>
<body>
<h1>Hello ${=obj.who}!</h1>
<h3>Self Version: ${obj.self_version}</h3>
<h3>Nutz Version: ${obj.nutz_version}</h3>
</body>
</html>
@Singleton
class HomeController @Inject()(cc: ControllerComponents, config: Configuration) extends AbstractController(cc) {
val version = config.get[String]("version")
def index(who: String) = Action {
Ok(views.html.index(who, version, PlayVersion.current))
}
}
和其他所有实现不一样, Play 使用的是 Scala. 代码看上去非常少, 不过对我这个 Javaer 来说还是稍微有点眼生的感觉. Play 的实现也包括了展现 app 和框架版本号, play 没有使用定义在 sbt 脚步中的版本, 而是从 app 配置文件中获取版本号, 稍稍重复了一点.
Play 用来生成页面的模板有两个文件:
@(title: String)(content: Html)
<!DOCTYPE html>
<html lang="en">
<head>
<title>@title</title>
</head>
<body>
@content
</body>
</html>
@(who: String, version: String, playVersion: String)
@main("Hello World - Play"){
<h1>Hello @who</h1>
<div>@version</div>
<p>
Powered by Play @playVersion
</p>
}
Play 实现提供了自动化测试方案:
class UnitSpec extends PlaySpec with Results {
"HomeController" should {
"return a valid result with action" in {
val config = Configuration(ConfigFactory.load("application"))
val controller = new HomeController(stubControllerComponents(), config)
val result = controller.index("world")(FakeRequest())
status(result) mustEqual OK
contentAsString(result) must include ("Hello World")
}
}
}
class FunctionalSpec extends PlaySpec with GuiceOneAppPerSuite {
"HomeController" should {
"render the index page" in {
val home = route(app, FakeRequest(GET, "/?who=World")).get
status(home) mustBe Status.OK
contentType(home) mustBe Some("text/html")
contentAsString(home) must include ("Hello World")
}
}
}
class BrowserSpec extends PlaySpec
with OneBrowserPerTest
with GuiceOneServerPerTest
with HtmlUnitFactory
with ServerProvider {
"Application" should {
"work from within a browser" in {
go to ("http://localhost:" + port)
pageSource must include ("Hello World")
}
}
}
@RestService(name = " ")
public class HelloWorldService implements Service {
private String appVersion = "1.0.0";
@Override
public void init(AnyValue conf) {
String path = "/META-INF/maven/org.redkalex.htdi/htdi-hello/pom.properties";
InputStream in = this.getClass().getResourceAsStream(path);
if (in != null) {
Properties props = new Properties();
try {
props.load(in);
in.close();
} catch (Exception e) {
}
this.appVersion = props.getProperty("version", "未知");
}
}
@RestMapping(name = "index.html", auth = false)
public HttpResult<String> index(String who) throws IOException {
if (who == null || who.isEmpty()) who = "World";
String body = "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<head>\n"
+ "<meta charset=\"UTF-8\">\n"
+ "<title>Hello " + who + "</title>\n"
+ "</head>\n"
+ "<body>\n"
+ "<h1>Hello " + who + "!</h1>\n"
+ "<h3>Project Version: " + appVersion + "</h3>\n"
+ "<h3>Redkale Version: " + Redkale.getDotedVersion() + "</h3>\n"
+ "</body>\n"
+ "</html>";
return new HttpResult<>("text/html;charset=utf8", body);
}
}
Redkale 实现直接使用字串拼接输出而不是模板引擎. 对于 HelloWorld 这样的项目这样做完全没有问题. 不过稍微正式一点的项目这样会死人的吧. Redkale 的作者强调 Redkale 的应用更多偏向于数据服务, 同时也提到如果需要模板引擎应用可以很方便地集成三方产品.
Redkale 实现了展示应用和框架版本的需求.
@SpringBootApplication
public class App extends SpringBootServletInitializer implements WebApplicationInitializer {
public static void main( String[] args )
{
SpringApplication.run(App.class, args);
}
}
@Controller
public class HelloController {
@RequestMapping("/")
public ModelAndView sayHello() {
ModelAndView view = new ModelAndView("/sayHello.html");
view.addObject("message", "Hello world");
view.addObject("name", "Spring Boot");
view.addObject("version", "2.x");
return view;
}
}
SpringBoot 的实现也很容易理解. 使用了 2 个源代码文件, 一个是启动入口, 另一个是响应处理器.
SprintBoot 使用 Beetl 引擎来生成主页:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World</title>
</head>
<body>
<h1>${message}</h1>
Build By ${name} ${version}
</body>
</html>
SprintBoot 硬编码了框架版本, 没有显示应用版本.
SprintBoot 实现提供了单元测试.
@RunWith(SpringRunner.class)
@WebMvcTest(HelloController.class)
public class MvcTest{
@Autowired
private MockMvc mvc;
@Test
public void testHello() throws Exception {
String viewName = mvc.perform(get("/")).andReturn().getModelAndView().getViewName();
Assert.assertEquals("/sayHello.html", viewName);
}
}
这个测试条件比较有趣, 检查是否使用了正确的 view 模板, 并没有针对真正的需求进行测试
@RequestPath(value = "/")
public class HelloController {
@RequestPath(value = "")
public HttpResponse index(HttpRequest request, String who) throws Exception {
String name = StrUtil.isBlank(who) ? "World" : who;
String html = "<title>Hello " + name + "</title><h1>Hello " + name + "</h1>";
html += "<div>" + HowToDoTioStarter.APP_NAME + " " + P.p.getStr("app.version") + "</div>";
html += "<div>Powered by <a href='https://t-io.org' target='_blank'>" + HttpConst.SERVER_INFO + " " + SysConst.TIO_CORE_VERSION + "</a></div>";
return Resps.html(request, html);
}
}
public class P {
public static Props p = new Props("classpath:app.properties");
}
public class HowToDoTioStarter {
public static final String APP_NAME = "how to do it : tio-mvc";
public static HttpConfig httpConfig;
public static HttpRequestHandler requestHandler;
public static HttpServerStarter httpServerStarter;
public static ServerGroupContext serverGroupContext;
public static void init() throws Exception {
httpConfig = new HttpConfig(P.p.getInt("http.port"), null, null, null);
// httpConfig.setPageRoot(P.p.getStr("http.page"));//html/css/js等的根目录,支持classpath:,也支持绝对路径
// httpConfig.setMaxLiveTimeOfStaticRes(P.p.getInt("http.maxLiveTimeOfStaticRes"));
// httpConfig.setPage404(P.p.getStr("http.404"));
// httpConfig.setPage500(P.p.getStr("http.500"));
httpConfig.setUseSession(false);
httpConfig.setCheckHost(false);
String[] scanPackages = new String[] { HelloController.class.getPackage().getName() };//tio mvc需要扫描的根目录包,会递归子目录
Routes routes = new Routes(scanPackages);
requestHandler = new DefaultHttpRequestHandler(httpConfig, routes);
httpServerStarter = new HttpServerStarter(httpConfig, requestHandler);
serverGroupContext = httpServerStarter.getServerGroupContext();
serverGroupContext.setUseQueueSend(true);
httpServerStarter.start(); //启动http服务器
}
public static void main(String[] args) throws Exception {
init();
}
}
TIO-MVC 的实现代码行数和 JFinal 实现相近. 控制器本身的代码相对简单, 只是实现中有一些 boilerplate 代码, 和其他实现比较起来稍显臃肿.
和 Redkale 一样, TIO-MVC 的实现选择使用字串拼接来生成主页; 另外 TIO-MVC 的应用版本是硬编码在代码中的.
ActFramework 提供了不同脚本来实现不同模式的启动:
使用 e2e 模式启动应用可以启动后立刻运行自动化测试脚本:
Blade 没有提供启动脚本, 不过 README 文件提供了启动步骤
在启动 JFinal 项目的时候我们遇到了一点麻烦, 因为 JFinal build 的包是用来部署到 servlet 容器里面的, 我们不太想去和 Tomcat 之类的东西打交道, 所以在 JFinal 项目里面添加了插件:
<plugins>
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>8.1.8.v20121106</version>
<configuration>
<stopKey>stop</stopKey>
<stopPort>5599</stopPort>
<webAppConfig>
<contextPath>/</contextPath>
</webAppConfig>
<scanIntervalSeconds>5</scanIntervalSeconds>
<connectors>
<connector implementation="org.eclipse.jetty.server.nio.SelectChannelConnector">
<port>80</port>
<maxIdleTime>60000</maxIdleTime>
</connector>
</connectors>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<executable>java</executable>
<arguments>
<argument>-Xms512m</argument>
<argument>-Xmx512m</argument>
<argument>-classpath</argument>
<classpath/>
<argument>htdi.jfinal.hellworld.config.ProjectConfig</argument>
</arguments>
</configuration>
</plugin>
</plugins>
之后我们就可以直接启动了:
Nutz 也是通过 README 文件提供了启动步骤. Nutz 启动信息比较多, 没法截屏:
luog@luog-X510UQR:~/p/htdi/nutz-hello-world$ mvn -q clean compile nutzboot:run
[INFO ] 07:55:50.685 org.nutz.boot.banner.SimpleBannerPrinter.printBanner(SimpleBannerPrinter.java:34) -
_ _ ______ ___
| \ | || ___ \ ______ ______ ______ ______ ______| \ \
| \| || |_/ / |______|______|______|______|______| |\ \
| . ` || ___ \ ______ ______ ______ ______ ______| | > >
| |\ || |_/ / |______|______|______|______|______| |/ /
\_| \_/\____/ |_/_/
:: Nutz Boot :: (2.3-SNAPSHOT)
[INFO ] 07:55:50.736 org.nutz.ioc.loader.annotation.AnnotationIocLoader.<init>(AnnotationIocLoader.java:50) - > scan 'htdi.nutz.helloworld'
[INFO ] 07:55:50.738 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'mainLauncher ' - htdi.nutz.helloworld.MainLauncher
[INFO ] 07:55:50.740 org.nutz.ioc.loader.annotation.AnnotationIocLoader.<init>(AnnotationIocLoader.java:50) - > scan 'org.nutz.boot.starter'
[INFO ] 07:55:50.750 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'nutFilterStarter ' - org.nutz.boot.starter.nutz.mvc.NutFilterStarter
[INFO ] 07:55:50.753 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'whaleFilterStarter ' - org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter
[INFO ] 07:55:50.754 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'jettyStarter ' - org.nutz.boot.starter.jetty.JettyStarter
[INFO ] 07:55:50.765 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'nbServletContextListener ' - org.nutz.boot.starter.servlet3.NbServletContextListener
[INFO ] 07:55:50.767 org.nutz.ioc.loader.annotation.AnnotationIocLoader.addClass(AnnotationIocLoader.java:98) - > add 'jstViewStarter ' - org.nutz.boot.starter.jst.JstViewStarter
[INFO ] 07:55:50.777 org.nutz.boot.NbApp.prepare(NbApp.java:279) - Configure Manual:
|id |key |required |Possible Values |Default |Description | starters|
|----|----------------------------------------|----------|--------------------|----------|--------------------|----------------------------------------|
|0 |jetty.contextPath |no | |/ |上下文路径 |org.nutz.boot.starter.jetty.JettyStarter|
|1 |jetty.gzip.enable |no | |false |是否启用gzip |org.nutz.boot.starter.jetty.JettyStarter|
|2 |jetty.gzip.level |no | |-1 |gzip压缩级别 |org.nutz.boot.starter.jetty.JettyStarter|
|3 |jetty.gzip.minContentSize |no | |512 |gzip压缩最小触发大小 |org.nutz.boot.starter.jetty.JettyStarter|
|4 |jetty.host |no | |0.0.0.0 |监听的ip地址 |org.nutz.boot.starter.jetty.JettyStarter|
|5 |jetty.http.idleTimeout |no | |300000 |空闲时间,单位毫秒 |org.nutz.boot.starter.jetty.JettyStarter|
|6 |jetty.httpConfig.blockingTimeout |no | |-1 |阻塞超时 |org.nutz.boot.starter.jetty.JettyStarter|
|7 |jetty.httpConfig.headerCacheSize |no | |8192 |头部缓冲区大小 |org.nutz.boot.starter.jetty.JettyStarter|
|8 |jetty.httpConfig.maxErrorDispatches |no | |10 |最大错误重定向次数 |org.nutz.boot.starter.jetty.JettyStarter|
|9 |jetty.httpConfig.outputAggregationSize |no | |8192 |输出聚合大小 |org.nutz.boot.starter.jetty.JettyStarter|
|10 |jetty.httpConfig.outputBufferSize |no | |32768 |输出缓冲区大小 |org.nutz.boot.starter.jetty.JettyStarter|
|11 |jetty.httpConfig.persistentConnectionsEnabled|no | |true |是否启用持久化连接 |org.nutz.boot.starter.jetty.JettyStarter|
|12 |jetty.httpConfig.requestHeaderSize |no | |8192 |请求的头部最大值 |org.nutz.boot.starter.jetty.JettyStarter|
|13 |jetty.httpConfig.responseHeaderSize |no | |8192 |响应的头部最大值 |org.nutz.boot.starter.jetty.JettyStarter|
|14 |jetty.httpConfig.securePort |no | | |安全协议的端口,例如8443 |org.nutz.boot.starter.jetty.JettyStarter|
|15 |jetty.httpConfig.secureScheme |no | | |安全协议,例如https |org.nutz.boot.starter.jetty.JettyStarter|
|16 |jetty.httpConfig.sendDateHeader |no | |true |是否发送日期信息 |org.nutz.boot.starter.jetty.JettyStarter|
|17 |jetty.httpConfig.sendServerVersion |no | |true |是否发送jetty版本号 |org.nutz.boot.starter.jetty.JettyStarter|
|18 |jetty.maxFormContentSize |no | |1gb |表单最大尺寸 |org.nutz.boot.starter.jetty.JettyStarter|
|19 |jetty.page.404 |no | | |自定义404页面,同理,其他状态码也是支持的|org.nutz.boot.starter.jetty.JettyStarter|
|20 |jetty.page.java.lang.Throwable |no | | |自定义java.lang.Throwable页面,同理,其他异常也支持|org.nutz.boot.starter.jetty.JettyStarter|
|21 |jetty.port |no | |8080 |监听的端口 |org.nutz.boot.starter.jetty.JettyStarter|
|22 |jetty.staticPath |no | | |额外的静态文件路径 |org.nutz.boot.starter.jetty.JettyStarter|
|23 |jetty.staticPathLocal |no | | |静态文件所在的本地路径 |org.nutz.boot.starter.jetty.JettyStarter|
|24 |jetty.threadpool.idleTimeout |no | |60000 |线程池idleTimeout,单位毫秒 |org.nutz.boot.starter.jetty.JettyStarter|
|25 |jetty.threadpool.maxThreads |no | |500 |线程池最大线程数maxThreads |org.nutz.boot.starter.jetty.JettyStarter|
|26 |jetty.threadpool.minThreads |no | |200 |线程池最小线程数minThreads |org.nutz.boot.starter.jetty.JettyStarter|
|27 |jetty.welcome_files |no | |index.html,index.htm,index.do|WelcomeFile列表 |org.nutz.boot.starter.jetty.JettyStarter|
|28 |nutz.mvc.whale.enc.input |no | |UTF-8 |在其他Filter之前设置input编码|org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter|
|29 |nutz.mvc.whale.enc.output |no | |UTF-8 |在其他Filter之前设置output编码|org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter|
|30 |nutz.mvc.whale.http.hidden_method_param |no | | |隐形http方法参数转换所对应的参数名 |org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter|
|31 |nutz.mvc.whale.http.method_override |no | |false |是否允许使用X-HTTP-Method-Override|org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter|
|32 |nutz.mvc.whale.upload.enable |no | |false |是否启用隐形Upload支持 |org.nutz.boot.starter.nutz.mvc.WhaleFilterStarter|
|33 |web.session.timeout |no | |30 |Session空闲时间,单位分钟 |org.nutz.boot.starter.jetty.JettyStarter|
[INFO ] 07:55:50.782 org.nutz.ioc.impl.NutIoc.<init>(NutIoc.java:130) - ... NutIoc init complete
[INFO ] 07:55:50.825 org.eclipse.jetty.util.log.Log.initialized(Log.java:193) - Logging initialized @2378ms to org.eclipse.jetty.util.log.Slf4jLog
[INFO ] 07:55:50.987 org.eclipse.jetty.server.Server.doStart(Server.java:374) - jetty-9.4.11.v20180605; built: 2018-06-05T18:24:03.829Z; git: d5fc0523cfa96bfebfbda19606cad384d772f04c; jvm 1.8.0_171-b11
[WARN ] 07:55:51.173 org.eclipse.jetty.annotations.AnnotationParser.asmVersion(AnnotationParser.java:95) - Unknown asm implementation version, assuming version 393216
[INFO ] 07:55:51.173 org.eclipse.jetty.annotations.AnnotationConfiguration.scanForAnnotations(AnnotationConfiguration.java:489) - Scanning elapsed time=0ms
[INFO ] 07:55:51.177 org.eclipse.jetty.webapp.StandardDescriptorProcessor.visitServlet(StandardDescriptorProcessor.java:283) - NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
[INFO ] 07:55:51.185 org.eclipse.jetty.server.session.DefaultSessionIdManager.doStart(DefaultSessionIdManager.java:365) - DefaultSessionIdManager workerName=node0
[INFO ] 07:55:51.185 org.eclipse.jetty.server.session.DefaultSessionIdManager.doStart(DefaultSessionIdManager.java:370) - No SessionScavenger set, using defaults
[INFO ] 07:55:51.187 org.eclipse.jetty.server.session.HouseKeeper.startScavenging(HouseKeeper.java:149) - node0 Scavenging every 660000ms
[INFO ] 07:55:51.202 org.nutz.mvc.NutFilter._init(NutFilter.java:85) - NutFilter[nutz] starting ...
[INFO ] 07:55:51.207 org.nutz.mvc.impl.NutLoading.load(NutLoading.java:55) - Nutz Version : 1.r.66-20180614
[INFO ] 07:55:51.207 org.nutz.mvc.impl.NutLoading.load(NutLoading.java:56) - Nutz.Mvc[nutz] is initializing ...
[INFO ] 07:55:51.207 org.nutz.mvc.config.AbstractNutConfig.getAppRoot(AbstractNutConfig.java:82) - /WEB-INF/ not Found?!
[INFO ] 07:55:51.210 org.nutz.mvc.impl.NutLoading.evalUrlMapping(NutLoading.java:159) - Build URL mapping by org.nutz.mvc.impl.UrlMappingImpl ...
[INFO ] 07:55:51.232 org.nutz.mvc.impl.NutActionChainMaker.getProcessorByName(NutActionChainMaker.java:72) - Optional processor class not found, disabled : org.nutz.integration.shiro.NutShiroProcessor
[INFO ] 07:55:51.254 org.nutz.mvc.impl.NutActionChainMaker.getProcessorByName(NutActionChainMaker.java:72) - Optional processor class not found, disabled : org.nutz.plugins.validation.ValidationProcessor
[INFO ] 07:55:51.262 org.nutz.mvc.impl.NutLoading.evalUrlMappingluog@luog-X510UQR:~/p/htdi/nutz-hello-world$ mvn -q clean compile nutzboot:run
(NutLoading.java:221) - Found 1 module methods
[INFO ] 07:55:51.263 org.nutz.mvc.impl.NutLoading.load(NutLoading.java:141) - Nutz.Mvc[nutz] is up in 56ms
[INFO ] 07:55:51.277 org.eclipse.jetty.server.handler.ContextHandler.doStart(ContextHandler.java:851) - Started o.e.j.w.WebAppContext@d2b229c{/,[],AVAILABLE}
[INFO ] 07:55:51.288 org.eclipse.jetty.server.AbstractConnector.doStart(AbstractConnector.java:289) - Started ServerConnector@32a11092{HTTP/1.1,[http/1.1]}{127.0.0.1:8080}
[INFO ] 07:55:51.288 org.eclipse.jetty.server.Server.doStart(Server.java:411) - Started @2842ms
[INFO ] 07:55:51.291 org.nutz.boot.NbApp.execute(NbApp.java:213) - NB started : 702ms
Play 的实现使用了 sbt, 第一次运行简直就是灾难, 花了至少半个小时才能启动. 不过第一次之后就都很好了:
和大部分实现项目一样, Redkale 在 README 中提供了如何启动应用的方法. 我们可以很容易启动 Redkale 项目:
启动 SpringBoot 项目没有什么问题:
TIO-MVC 实现稍微有一点不一样. 使用 mvn clean package
构建项目包之后我们需要到 /target/htdi-tio-helloworld
目录然后运行 startup.sh
启动应用:
注意
1 SpringBoot 提供了单元测试用例, 不过该测试只检查是否加载了正确的模板文件, 并没有检查输出是否满足需求 2 测试环境: 操作系统: LinuxMint 19, 硬件: i7 8550U + 16GB RAM + SSD