2019独角兽企业重金招聘Python工程师标准>>>
1. 介绍
HelloWorld 是一个HowToDoIt 组织的第一个项目, 一个简单的 MVC 展示应用. 实现项目需要响应发送到 GET /
端点的请求并显示一个主页
- 显示
Hello World
- 其中World
可以被who
查询参数的值替代 - [可选] 显示应用版本
- [可选] 显示框架版本
- [可选] 使用模板引擎生成主页
2. 实现
下面是所有参与项目的链接:
- ActframeWork
- 作者: 老码农 - ActFramework 框架作者
- Blade
- 作者: 魔王不造反 - Blade 框架作者
- JFinal
- 作者: 天蓬元帅 - JFinal 资深用户
- Nutz
- 作者: Wendal - nutz 资深用户
- Play
- 作者: joymufeng - PlayFramework 资深用户
- Redkale
- 作者: Redkale - Redkale 框架作者
- SpringBoot
- 作者: 闲.大赋 - SpringBoot 畅销书作者, beetl 模板引擎和 beetlsql 数据库访问库作者
- TIO-MVC
- 作者: talent-tan - tio 作者,码云封面人物
3. 源码一览
3.1 ActFramework
public class AppEntry {/*** The home (`/`) endpoint.** @param who* request query parameter to specify the hello target.* default value is `World`.*/@GetActionpublic 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 Hellointeractions:- description: send request to the app without parameterrequest:method: GETurl: /response:html:h1: Hello Worldhead title:- contains: Hello World- contains: ActFrameworkp:<any>:- contains: Powered by ActFramework- description: send request to the app with [who = ActFramework]request:method: GETurl: /?who=ActFrameworkresponse:html:h1: Hello ActFramework
3.2 Blade
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
文件中获取应用版本信息, 而是在额外的配置文件中定义应用版本.
3.3 JFinal
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 提供了框架版本号,但没有提供应用版本号
3.4 Nutz
@IocBean
public class MainLauncher {@Injectprotected 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>
3.5 Play
@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 OKcontentAsString(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")).getstatus(home) mustBe Status.OKcontentType(home) mustBe Some("text/html")contentAsString(home) must include ("Hello World")}}
}
class BrowserSpec extends PlaySpecwith OneBrowserPerTestwith GuiceOneServerPerTestwith HtmlUnitFactorywith ServerProvider {"Application" should {"work from within a browser" in {go to ("http://localhost:" + port)pageSource must include ("Hello World")}}
}
3.6 Redkale
@RestService(name = " ")
public class HelloWorldService implements Service {private String appVersion = "1.0.0";@Overridepublic 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 实现了展示应用和框架版本的需求.
3.7 SprintBoot
@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{@Autowiredprivate MockMvc mvc;@Testpublic void testHello() throws Exception {String viewName = mvc.perform(get("/")).andReturn().getModelAndView().getViewName();Assert.assertEquals("/sayHello.html", viewName);}
}
这个测试条件比较有趣, 检查是否使用了正确的 view 模板, 并没有针对真正的需求进行测试
3.8 TIO-MVC
@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 的应用版本是硬编码在代码中的.
4. 启动
4.1 启动 ActFramework
ActFramework 提供了不同脚本来实现不同模式的启动:
4.1.1 开发模式
4.1.2 e2e 测试模式
使用 e2e 模式启动应用可以启动后立刻运行自动化测试脚本:
4.1.3 产品模式
4.2 Blade
Blade 没有提供启动脚本, 不过 README 文件提供了启动步骤
4.3 JFinal
在启动 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>
之后我们就可以直接启动了:
4.4 Nutz
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
4.5 Play
Play 的实现使用了 sbt, 第一次运行简直就是灾难, 花了至少半个小时才能启动. 不过第一次之后就都很好了:
4.6 Redkale
和大部分实现项目一样, Redkale 在 README 中提供了如何启动应用的方法. 我们可以很容易启动 Redkale 项目:
4.7 SpringBoot
启动 SpringBoot 项目没有什么问题:
4.8 TIO-MVC
TIO-MVC 实现稍微有一点不一样. 使用 mvn clean package
构建项目包之后我们需要到 /target/htdi-tio-helloworld
目录然后运行 startup.sh
启动应用:
5. 一些数据比较
注意
1 SpringBoot 提供了单元测试用例, 不过该测试只检查是否加载了正确的模板文件, 并没有检查输出是否满足需求 2 测试环境: 操作系统: LinuxMint 19, 硬件: i7 8550U + 16GB RAM + SSD