Skip to main content

控制器

本框架基于Jfinal开发,Controller基本兼容原有工作模式。Controller为MVC模式中的控制器,基于本框架的Web应用的控制器需要继承该类。Controller是定义Action方法的地方,是组织Action的一种方式,一个Controller可以包含多个Action。

Action 定义#

Action 是请求的最小单位。Action 方法必须在 Controller 中定义,且必须是 public 可见性。

@RequestMapping(value = "/")
public class FirstController extends Controller {
@RequestMapping
public Result<String> index() {
return Success.of("hello world !");
}
}

路由映射#

通过注解映射#

  • @RequestMapping注解加在控制器类上,用来绑定域名及映射主URL路径,@RequestMapping注解加在控制器的方法上,用来映射子路径。
  • @RequestMapping的host参数和subHost参数,只能用在控制器类上,用来指定绑定的主域名和子域名。前提条件是域名已经指向当前服务器IP地址。
    域名绑定

    域名绑定后,只有通过设置的域名才能访问到该控制器的方法上。域名绑定方便进行业务拆分,例如按省份拆分,同一个业务按省份分别用子域名绑定在不同的控制器。

  • @RequestMapping的value参数,可以用于控制器类及其方法上,用来指定映射路径,支持一次指定多个映射路径。
  • 方法上的@RequestMapping的value参数如果为空,那么就会默认以方法名为value进行映射,如果方法名为index会被映射到"/"。
  • @RequestMapping的method参数,只能用于方法上,用于指定请求方式,默认值为GET和POST,即如果不指定将接受GET和POST的请求。
  • @RequestMapping的produces参数,只能用于方法上,用于指定该请求输出类型,有Json, Xml, General(文本或HTML输出)可选,默认值为Json。
  • @RequestMapping的viewPath参数,可以用于控制器类及其方法上,用于指定该控制器模版引擎的模版路径。

程序配置映射#

下面是一个控制器类,定义了2个方法,未做任何注解映射

public class ManualController extends Controller {
public void index() {
renderText("this is a default index action, mapped manually");
}
public void test() {
renderText("this is a test action, mapped manually");
}
}

这里我们演示如何通过程序映射上面的控制器,在当前应用的主类中添加initialize()方法,该方法会在当前应用启动时初始化。

public class Main extends Application {
@Override
protected void initialize() {
route().add(new RouteConfig().addRoute("/", ManualController.class, "index", null)
.addRoute("/test/", ManualController.class, "test", null));
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

通过向路由配置中添加2组新的路由,指定映射URL路径和对应的控制器及方法名。通过RouteConfig可以设置和注解相同的各类参数。 注意,此时如果有其他控制器也映射在了"/"和"/test/",那么此前的映射会被覆盖。

模版#

尽管模版的应用当前已经不流行,但在许多场合还是一种比较方便快捷的开发方式。使用模版前需要在配置文件中设置viewPath,即模版路径。模版文件的扩展名默认为.html,可以通过viewFileExt配置项设置。

  • 如果模版是不需要变更的可以在当前项目的resources下建立一个模版目录存放,随后打包到Jar包中。例如:我们在当前项目的resources下建立一个名为view的子文件夹,用于存放模版文件。然后需要配置文件中设置viewPath=/view,这里的/相对于resources资源文件夹。
  • 如果模版文件有可能需要变更的,最好在项目的工作空间中建立一个模版目录存放,随后还可以随时修改。例如:我们在当前项目工作空间ready.work文件夹中建立一个名为view的子文件夹,用于存放模版文件。然后需要配置文件中设置viewPath=/view,这里的/相对于ready.work工作空间。

以上两者存放模版的方式,以下面一种优先,即如果在ready.work工作空间中已经存在了配置文件中指定的模版文件路径,那么就会使用该路径定位模版文件。如果ready.work工作空间中不存在设定的模版路径,默认会从resources资源文件夹去定位模版文件。请看实例:

通过配置文件指定模版路径:

bootstrap-dev.yml
# configuration for dev environment
---
readyWork:
viewPath: /view
#viewFileExt: .html

通过程序设定模版路径:

public class Main extends Application {
@Override
protected void initialize() {
applicationConfig().setViewPath("/view");
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

然后在对应路径的view文件夹下放置一个名为template.html的模版文件,最后在控制器中添加方法调用该模版文件。

@RequestMapping(value = "/")
public class MvcController extends Controller {
@RequestMapping
public Result<String> index() {
return Success.of("hello world !");
}
@RequestMapping()
public void template() {
render("template");
}
}

如果控制器和方法中的注解也指定了模版路径,那么该路径会相对于前面已经设置的主模版路径"/view"。 由于模版引擎继承自Enjoy Template Engine,模版语法请自行参阅https://jfinal.com/doc/6-1

请求参数#

常规参数#

在控制器中我们可以通过String getParam(String name)方法和支持默认值的String getParam(String name, String defaultValue)方法可以获取http请求中的参数, 通常我们获取到的参数是String类型,如果我们希望能一步到位的获取并转换为指定类型,可以使用getParamToInt、getParamToBoolean、getParamToDate、getParamToLong系列方法获取参数并直接转换为指定类型。

路径参数#

路径参数的支持需要映射的路径中用大括号指定,例如:@RequestMapping("/company/{companyId}/worker/{workerId}"),这里指定了companyId和workerId两个路径参数,要获取这两个参数需要用getPathParam方法。 路径参数可以在控制器的映射路径中作为全局路径参数,也可以只在控制器下的方法中作为局部路径参数。

参数注入#

通过中控制器中方法的参数前面加上注解@Param("参数名"),可以自动注入外部请求的参数。但该方式目前只支持常规参数注入,不支持路径参数注入。

综合实例#

我们在上面的控制器例子中,添加一个param的方法,该方法的综合应用了上述特性。

@RequestMapping(value = "/")
public class MvcController extends Controller {
@RequestMapping
public Result<String> index() {
return Success.of("hello world !");
}
// http://127.0.0.1:8080/param/param1/param2/?age=99&name=aaa
@RequestMapping("/param/{p1}/{p2}/")
public Result<String> param( @Param("name") String name) {
String p1 = getPathParam("p1");
String p2 = getPathParam("p2");
int age = getParamToInt("age", 100);
return Success.of("p1=" + p1 + ", p2=" + p2 + ", name=" + name + ", age=" + age);
}
@RequestMapping()
public void template() {
render("template");
}
}

假设该服务绑定在127.0.0.1的8080端口,用浏览器访问该路径(http://127.0.0.1:8080/param/param1/param2/?age=99&name=aaa),将得到p1=param1, p2=param2, name=aaa, age=99

返回及渲染#

本框架封装了对Controller返回结果的渲染的过程,通常有两种结果需要返回给请求者,要么是数据要么是异常。如果需要使用框架的自动渲染,在定义Action的时候返回类型需要是Result类型。 正常数据用Success对象封装返回,失败或异常用Failure对象封装返回,框架会根据返回内容自动渲染。Json是当前最通用的API数据交换格式,框架默认将结果渲染为Json格式,通过指定RequestMapping的Produces,也支持以XML格式返回。 需要注意的是Failure返回时,需要先定义好错误码,错误码和错误信息有统一配置文件管理(默认为status.yml),这是框架强制要求。 如果不想通过框架自动渲染,由于本框架基于Jfinal开发,也保持了对Jfinal的渲染兼容。在定义Action的时候返回值设为void,然后通过render、renderJson、renderText、renderFile、renderHtml、renderXml等系列方法进行结果渲染。

输出二维码#

@RequestMapping(value = "/")
public class MvcController extends Controller {
@RequestMapping
public void QrCode(){
renderQrCode("LvWeiHua", 200, 200);
}
}

文件上传#

文件上传一般通过multipart/form-data类型的POST请求向服务器发送文件内容,上传文件大小受到2个参数的制约,一个是WEB服务器的MaxEntitySize和每个上传文件的maxUploadFileSize,可以通过配置进行设定:

public class Main extends Application {
@Override
protected void initialize() {
// 设置文件上传的保持路径,默认为当前工作空间的upload子目录,可以通过下面的方式修改路径
//applicationConfig().setUploadPath("/upload");
// 默认情况下文件上传是临时保存在内存(默认设置,小于100K的文件)或临时文件夹中的,然后需要通过程序在表单提交后保存到正式路径。
// 这样会从临时文件夹再保存到upload文件夹多了IO操作,这里可以设定不进行临时保存,而直接保存到upload文件夹中,该功能默认是关闭的。
//applicationConfig().setDirectlySaveUploadFile(true);
// 每次FORM提交的总大小,指整个表单的全部字段及文件的总计字节数,这里设置容许最大 20M 的表单
serverConfig().setMaxEntitySize(20 * 1024 * 1024);
// 每次提交表单中单个文件的最大字节数,这里设置容许最大 10M 的文件上传
applicationConfig().setMaxUploadFileSize(10 * 1024 * 1024);
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

表单提交后,文件就已经保存在服务器内存或临时文件中了,在Controller中可以通过getFile("name")或getFiles()方法获取上传的文件内容,再进一步保存到目标路径。

@RequestMapping(value = "/")
public class MvcController extends Controller {
@RequestMapping
public void upload() throws IOException {
if(getRequest().getMethod().equals(RequestMethod.POST)) {
String result = "";
for(String name : getFiles().keySet()){
result += name + " => " + getFiles().get(name).originalFileName + " => ";
if(getFiles().get(name).fileItem.isInMemory()) {
// 内存中的文件内容直接保存到指定路径的文件中
//getFiles().get(name).fileItem.write(Ready.root().resolve(Ready.getBootstrapConfig().getUploadPath()).resolve("filename"));
result += "store in memory temporarily";
} else {
// 临时文件夹中的文件,直接移动该文件到指定路径,也可以像上面一样进行保存,但如果文件很大,这样会产生很多不必要的IO操作
//Files.move(getFiles().get(name).file, Ready.root().resolve(Ready.getBootstrapConfig().getUploadPath()).resolve("filename"));
result += "store in file temporarily: " + getFiles().get(name).fileItem.getFile().toString();
}
result += " " + getFiles().get(name).fileItem.getFileSize() + " bytes\r\n";
}
renderText(result);
} else {
// upload模版就只有一个Form表单,用来上传文件
render("upload");
}
}
}

文件下载#

@RequestMapping(value = "/")
public class MvcController extends Controller {
@RequestMapping
public void downloadFromResource(){
// 从Jar包或ClassPath中的resources文件夹中读取文件
renderFile(new File(Ready.getClassLoader().getResource("static/files/test.dat").getFile()), "测试文件.dat");
}
@RequestMapping
public void downloadFromPath(){
// 从工作空间下的子文件夹中读取文件
renderFile(Ready.root().resolve("static/files/test.dat").toFile(), "测试文件.dat");
}
}
下载限流

如果需要对下载进行限流,可以参考安全策略中关于网络安全-连接限制相关文档。

静态文件映射#

经常需要将静态文件路径映射到某个url地址,下面的例子将/favicon.ico地址映射到工作空间下面的/static/favicon.ico,将/download/地址映射到工作空间下面的/download/文件夹。

public class Main extends Application {
@Override
protected void initialize() {
applicationConfig().getStaticResource().setMapping("/favicon.ico", "/static/favicon.ico")
.setMapping("/download/","/download/");
handler().addHandler(StaticResourceHandler.class);
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

文件管理器#

有时候需要有一个便捷快速的方法导出服务器静态文件目录供浏览和下载,下面实例映射URL地址/list到工作空间下的/static/files/文件夹,通过浏览器即可访问该文件夹下的文件及其子文件夹。

public class Main extends Application {
@Override
protected void initialize() {
applicationConfig().getStaticResource().getPathResource().addPath(
new PathResource().setPath("/list").setBasePath("/static/files/").setPrefix(true).setDirectoryListingEnabled(true));
handler().addHandler(PathResourceHandler.class);
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

静态资源绑定域名#

经常需要将图片文件夹或下载文件夹单独绑定到images.xxx.com、download.xxx.com这样的子域名下,方便随时变更服务器或者做负载均衡、CDN加速等部署,下面实例映射本地域名images.localhost的URL地址/到工作空间下的/static/images/文件夹。

public class Main extends Application {
@Override
protected void initialize() {
applicationConfig().getStaticResource().getVirtualHost().addHost(
new VirtualHost().setDomain("images.localhost")
.setPath("/")
.setBasePath("/static/images/")
.setDirectoryListingEnabled(true) // 如果不需要将文件都列出,可以去掉此项设置
);
handler().addHandler(VirtualHostHandler.class);
}
public static void main(String[] args) {
Ready.For(Main.class).Work(args);
}
}

注意,上面的例子需要自行修改hosts文件,添加images.localhost指向127.0.0.1地址才能访问。

Cookie#

在控制器中,我们可以通过setCookie和getCookie系列方法对Cookie进行操作。

Session#

在控制器中,我们可以通过getSession来获得session对象来对session进行操作,或者也可以直接用setSessionAttr和getSessionAttr系列方法对Session进行操作。

Request和Response对象#

在控制器中,我们可以通过getRequest()和getResponse()获得HttpRequest和HttpResponse对象。通过这两个对象可以对http请求的输入和输出进行更加底层的操作。

更多细节#

由于MVC相关内容较多,以上只列举部分重点内容。后续逐步完善更多具体的文档,并对重点内容进行专题描述。