MVC

MVC的概念

Controller

Controller是JFinal核心类之一,该类作为MVC模式中的控制器。基于JFinal的Web应用的控制器需要继承该类。Controller是定义Action方法的地点,是组织Action的一种方式,一个Controller可以包含多个Action。Controller是线程安全的。

JbootController

JbootController是扩展了JFinal Controller,在Jboot应用中,所有的控制器都应该继承至JbootController。

JbootController新增的普通方法:

方法调用 描述
isMoblieBrowser() 是否是手机浏览器
isWechatBrowser() 是否是微信浏览器
isIEBrowser() 是否是IE浏览器,低级的IE浏览器在ajax请求的时候,返回json要做特殊处理
isAjaxRequest() 是否是ajax请求
isMultipartRequest() 是否是带有文件上传功能的请求
getReferer() 获取来源网址器
getIPAddress() 获取用户的IP地址,这个决定于浏览器,同时做nginx等转发的时候要做好配置
getUserAgent() 获取http头的useragent
getBaseUrl() 获取当前域名
getUploadFilesMap() 获取当前上传的所有文件

新增关于FlashMessage的方法:

方法调用 描述
setFlashAttr() 设置 FlashMessage 的 key 和 value
setFlashMap() 把整个 map的key和value 设置到 FlashMessage
getFlashAttr() 获取 已经设置进去的FlashMessage 信息
getFlashAttrs() 获取 所有已经设置进去的 FlashMessage 信息

FlashMessage 是一种特殊的 attribute,用法和 setAttr 一样,唯一不同的是 setAttr 是用于当前页面渲染,而
setFlashAttr 是用于对 redirect 之后的页面进行渲染。

新增关于JWT的方法:

方法调用 描述
setJwtAttr() 设置 jwt 的 key 和 value
setJwtMap() 把整个 map的key和value 设置到 jwt
getJwtAttr() 获取 已经设置进去的 jwt 信息
getJwtAttrs() 获取 所有已经设置进去的 jwt 信息
getJwtPara() 获取客户端传进来的 jwt 信息,若 jwt 超时或者不被信任,那么获取到的内容为null

JWT简介: Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

JWT的相关配置

配置属性 描述
jboot.web.jwt.httpHeaderName 配置JWT的http头的key,默认为JWT
jboot.web.jwt.secret 配置JWT的密钥
jboot.web.jwt.validityPeriod 配置JWT的过期时间,默认不过期

@RquestMapping

RquestMapping是请求映射,也就是通过@RquestMapping注解,可以让某个请求映射到指定的控制器Controller里去。

使用@RquestMapping

使用@RquestMapping非常简单。只需要在Controller类添加上@RquestMapping注解即可。

例如:

  1. @RequestMapping("/")
  2. public class HelloController extends JbootController{
  3. public void index(){
  4. renderText("hello jboot");
  5. }
  6. }

我们在HelloController控制器上,添加了@RequestMapping(“/“)配置,也就是让当访问 http://127.0.0.1/的时候让HelloController控制的index()这个方法(action)来处理。

[注意]:

  • 访问http://127.0.0.1等同于http://127.0.0.1/
  • @RquestMapping 可以使用在任何的 Controller,并 不需要 这个Controller继承至JbootController。

Action

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

每个Action对应一个URL地址的映射:

  1. public class HelloController extends Controller {
  2. public void index() {
  3. renderText("此方法是一个action");
  4. }
  5. public String test() {
  6. return "index.html";
  7. }
  8. public String save(User user) {
  9. user.save();
  10. render("index.html");
  11. }
  12. }

以上代码中定义了三个 Action,分别是 HelloController.index()HelloController.test()HelloController.save(User user)

Action 可以有返回值,返回值可在拦截器中通过 invocation.getReturnValue() 获取到,以便进行 render 控制。

Action 可以带参数,可以代替 getPara、getBean、getModel 系列方法获取参数,使用 UploadFile 参数时可以代替 getFile 方法实现文件上传。这种传参方式还有一个好处是便于与 swagger 这类 第三方无缝集成,生成 API 文档。

注意: 带参数的Action必须在pom.xml文件里添加如下配置:

  1. <plugin>
  2. <groupId>org.apache.maven.plugins</groupId>
  3. <artifactId>maven-compiler-plugin</artifactId>
  4. <configuration>
  5. <source>1.8</source>
  6. <target>1.8</target>
  7. <encoding>UTF-8</encoding>
  8. <!--必须添加compilerArgument配置,才能使用JController方法带参数的功能-->
  9. <compilerArgument>-parameters</compilerArgument>
  10. </configuration>
  11. </plugin>

如果 action 形参是一个 model 或者 bean,原先通过 getBean(User.class, “”) 获取 时第二个参数为空字符串或 null,那么与之等价的形参注入只需要用一下@Para(“”)注解即可:

  1. public void save(@Para(“”)User user) {
  2. user.save();
  3. render("index.html");
  4. }

getPara 系列方法

Controller 供了 getPara 系列方法用来从请求中获取参数。getPara 系列方法分为两种类型。 第一种类型为第一个形参为 String 的 getPara 系列方法。该系列方法是对 HttpServletRequest.getParameter(String name) 的 封 装 , 这 类 方 法 都 是 转 调 了 HttpServletRequest.getParameter(String name)。第二种类型为第一个形参为 int 或无形参的 getPara 系列方法。该系列方法是去获取 urlPara 中所带的参数值。getParaMap 与 getParaNames 分别对应 HttpServletRequest 的 getParameterMap 与 getParameterNames。

方法调用 返回值
getPara(”title”) 返回页面表单域名为“title”参数值
getParaToInt(”age”) 返回页面表单域名为“age”的参数值并转为 int 型
getPara(0) 返回 url 请求中的 urlPara 参数的第一个值,如 http://localhost/controllerKey/method/v0-v1-v2 这个请求将 返回”v0”
getParaToInt(1) 返回 url 请求中的 urlPara 参数的第二个值并转换成 int 型,如 http://localhost/controllerKey/method/2-5-9 这个请求将返回 5
getParaToInt(2) http://localhost/controllerKey/method/2-5-N8 这个 请求将返回 -8。注意:约定字母 N 与 n 可以表示负 号,这对 urlParaSeparator 为 “-” 时非常有用。
getPara() 返回 url 请求中的 urlPara 参数的整体值,如 http://localhost/controllerKey/method/v0-v1-v2 这个 请求将返回”v0-v1-v2”

getBean 与 getModel 方法

getModel 用来接收页面表单域传递过来的 model 对象,表单域名称以”modelName.attrName”方式命名,getModel 使用的 attrName 必须与数据表字段名完全一样。getBean 方法用于支持传统 Java Bean,包括支持使用 jfnal 生成器生成了 getter、setter 方法的 Model,页面表单传参时使用与 setter 方法相一致的 attrName,而非数据表字段名。 getModel 与 getBean 区别在于前者使用数表字段名而后者使用与 setter 方法一致的属性名进行数据注入。建议优先使用 getBean 方法。

以下是一个简单的示例:

  1. // 定义Model,在此为Blog
  2. public class Blog extends JbootModel<Blog> {
  3. }
  4. // 在页面表单中采用modelName.attrName形式为作为表单域的name
  5. <form action="/blog/save" method="post">
  6. <input name="blog.title" type="text">
  7. <input name="blog.content" type="text">
  8. <input value=" 交" type="submit">
  9. </form>
  10. @RequestMapping("/blog")
  11. public class BlogController extends JbootController {
  12. public void save() {
  13. // 页面的modelName正好是Blog类名的首字母小写
  14. Blog blog = getModel(Blog.class);
  15. //如果表单域的名称为 "otherName.title"可加上一个参数来获取
  16. Blog blog = getModel(Blog.class, "otherName");
  17. //如果表单域的名称为 "title" 和 "content"
  18. Blog blog = getModel(Blog.class, "");
  19. }
  20. // 或者 也可以写如下代码,但是注意,只能写一个save方法
  21. public void save(Blog blog) {
  22. // do your something
  23. }
  24. }

上面代码中,表单域采用了”blog.title”、”blog.content”作为表单域的 name 属性,”blog”是类 文件名称”Blog”的首字母变小写,”title”是 blog 数据库表的 title 字段,如果希望表单域使用任 意的 modelName ,只需要在 getModel 时多添加一个参数来指定,例如: getModel(Blog.class, ”otherName”)。

render

渲染器,负责把内容输出到浏览器,在Controller中,提供了如下一些列render方法。

指令 描述
render(”test.html”) 渲染名为 test.html 的视图,该视图的全路径为”/path/test.html”
render(”/other_path/test.html”) 渲染名为 test.html 的视图,该视图的全路径为”/other_path/test.html”,即当参数以”/”开头时将采用绝对路径。
renderTemplate(”test.html”) 渲染名为 test.html 的视图,且视图类型为 JFinalTemplate。
renderFreeMarker(”test.html”) 渲 染 名 为 test.html 的视图 , 且 视图类型为FreeMarker。
renderJsp(”test.jsp”) 渲染名为 test.jsp 的视图,且视图类型为 Jsp。
renderVelocity(“test.html”) 渲染名为 test.html 的视图,且视图类型为 Velocity。
renderJson() 将所有通过 Controller.setAttr(String, Object)设置的变量转换成 json 数据并渲染。
renderJson(“users”, userList) 以”users”为根,仅将 userList 中的数据转换成 json数据并渲染。
renderJson(user) 将 user 对象转换成 json 数据并渲染。
renderJson(“{\”age\”:18}” ) 直接渲染 json 字符串。
renderJson(new String[]{“user”, “blog”}) 仅将 setAttr(“user”, user)与 setAttr(“blog”, blog)设置的属性转换成 json 并渲染。使用 setAttr 设置的其它属性并不转换为 json。
renderFile(“test.zip”); 渲染名为 test.zip 的文件,一般用于文件下载
renderText(“Hello Jboot”) 渲染纯文本内容”Hello Jboot”。
renderHtml(“Hello Html”) 渲染 Html 内容”Hello Html”。
renderError (404 , “test.html”) 渲染名为 test.html 的文件,且状态为 404。
renderError (500 , “test.html”) 渲染名为 test.html 的文件,且状态为 500。
renderNull() 不渲染,即不向客户端返回数据。
render(new MyRender()) 使用自定义渲染器 MyRender 来渲染。

session 与 分布式session

使用session非常简单,直接在Controller里调用getSessionAttr(key)setSessionAttr(key,value) 就可以。

分布式session

在Jboot的设计中,分布式的session是依赖分布式缓存的,jboot中,分布式缓存提供了3种方式:

  1. ehcache
  2. redis
  3. ehredis: 基于ehcache和redis实现的二级缓存框架。

所以,在使用jboot的分布式session之前,需要在jboot.properties配置上jboot分布式的缓存。

例如:

  1. jboot.cache.type=redis
  2. jboot.cache.redis.host = 127.0.0.1
  3. jboot.cache.redis.password = 123456
  4. jboot.cache.redis.database = 1

配置好缓存后,直接在Controller里调用getSessionAttr(key)setSessionAttr(key,value) 即可。

注意: session都是走缓存,如果jboot配置的缓存是ehcache(或者 ehredis),请注意在ehcache.xml上添加名为 SESSION 的缓存节点。

限流和流量控制

在Jboot中,默认提供了4个注解进行流量管控。4个注解代表着四个不同的流量管控方案,他们分别是:

指令 描述
EnableConcurrencyLimit 限制当前Action的并发量
EnablePerIpLimit 限制每个IP的每秒访问量
EnablePerUserLimit 限制每个用户的访问量
EnableRequestLimit 限制总体每秒钟可以通过的访问量

例如:

  1. @RequestMapping("/limitation")
  2. public class LimitationDemo extends JbootController {
  3. public static void main(String[] args) {
  4. Jboot.setBootArg("jboot.limitation.webPath","/limitation/view");
  5. Jboot.run(args);
  6. }
  7. public void index() {
  8. renderText("render ok");
  9. }
  10. /**
  11. * 所有的请求,每1秒钟只能访问一次
  12. */
  13. @EnableRequestLimit(rate = 1)
  14. public void request() {
  15. renderText("request() render ok");
  16. }
  17. /**
  18. * 所有的请求,并发量为1个
  19. */
  20. @EnableConcurrencyLimit(rate = 1)
  21. public void con() {
  22. try {
  23. Thread.sleep(2000);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. renderText("con() render ok");
  28. }
  29. /**
  30. * 所有的请求,每1秒钟只能访问一次
  31. * 被限制的请求,自动跳转到 /limitation/request2
  32. */
  33. @EnableRequestLimit(rate = 1, renderType = LimitRenderType.REDIRECT, renderContent = "/limitation/request2")
  34. public void request1() {
  35. renderText("request1() render ok");
  36. }
  37. public void request2() {
  38. renderText("request2() render ok");
  39. }
  40. /**
  41. * 每个用户,每5秒钟只能访问一次
  42. */
  43. @EnablePerUserLimit(rate = 0.2)
  44. public void user() {
  45. renderText("user() render ok");
  46. }
  47. /**
  48. * 每个用户,每5秒钟只能访问一次
  49. * 被限制的请求,渲染文本内容 "被限制啦"
  50. */
  51. @EnablePerUserLimit(rate = 0.2, renderType = LimitRenderType.TEXT, renderContent = "被限制啦")
  52. public void user1() {
  53. renderText("user1() render ok");
  54. }
  55. /**
  56. * 每个IP地址,每5秒钟只能访问一次
  57. */
  58. @EnablePerIpLimit(rate = 0.2)
  59. public void ip() {
  60. renderText("ip() render ok");
  61. }
  62. }

以上代码和注释已经很清楚的描述了每个注解的意义,但是,针对已经上线的项目,使用@EnableXXXLimit进行流量控制并一定是有效的,很多时候我们需要针对突发流量进行限制和管控,因此,除了以上注解意外,Jboot提供了在线流量管理功能。

使用Jboot在线流量管理,首先配置上流量管理的URL地址,例如:

  1. jboot.limitation.webPath = /jboot/limitation

配置好,启动项目,访问 http://127.0.0.1:8080/jboot/limitation 我们可以看到如下内容:

  1. {
  2. "ipRates": {
  3. "/limitation/ip": {
  4. "enable": true,
  5. "rate": 0.2,
  6. "renderContent": "",
  7. "renderType": "",
  8. "type": "ip"
  9. }
  10. },
  11. "userRates": {
  12. "/limitation/user": {
  13. "enable": true,
  14. "rate": 0.2,
  15. "renderContent": "",
  16. "renderType": "",
  17. "type": "user"
  18. },
  19. "/limitation/user1": {
  20. "enable": true,
  21. "rate": 0.2,
  22. "renderContent": "被限制啦",
  23. "renderType": "text",
  24. "type": "user"
  25. }
  26. },
  27. "concurrencyRates": {
  28. "/limitation/con": {
  29. "enable": true,
  30. "rate": 1,
  31. "renderContent": "",
  32. "renderType": "",
  33. "type": "concurrency"
  34. }
  35. },
  36. "requestRates": {
  37. "/limitation/request1": {
  38. "enable": true,
  39. "rate": 1,
  40. "renderContent": "/limitation/request2",
  41. "renderType": "redirect",
  42. "type": "request"
  43. },
  44. "/limitation/request": {
  45. "enable": true,
  46. "rate": 1,
  47. "renderContent": "",
  48. "renderType": "",
  49. "type": "request"
  50. }
  51. }
  52. }

限流API

  1. 限流设置

    • 接口:/jboot/limitation/set
    • 参数:

      | 参数 | 描述 |
      | ——————- | ——-|
      | type | 限流类型:支持有 ip,user,request,concurrency,分别代表:单个IP每秒钟限流、单个用户每秒钟限流、每秒钟允许请求的数量,总体并发量设置 |
      | path |要对那个路径进行设置,例如 /user/aabb|
      | rate |设置的数值是多少|

  2. 关闭限流管控

    • 接口:/jboot/limitation/close
    • 参数:

      | 参数 | 描述 |
      | ——————- | ——-|
      | type | 限流类型:支持有 ip,user,request,concurrency,分别代表:单个IP每秒钟限流、单个用户每秒钟限流、每秒钟允许请求的数量,总体并发量设置 |
      | path |要对那个路径进行设置,例如 /user/aabb|

  3. 开启限流管控

    • 接口:/jboot/limitation/enable
    • 参数:

      | 参数 | 描述 |
      | ——————- | ——-|
      | type | 限流类型:支持有 ip,user,request,concurrency,分别代表:单个IP每秒钟限流、单个用户每秒钟限流、每秒钟允许请求的数量,总体并发量设置 |
      | path |要对那个路径进行设置,例如 /user/aabb|

注意:

  1. 通过限流API进行限流,所有的设置都会保存在内存里,因此如果重启服务器后,通过限流API进行限流的所有设置将会失效。
  2. 接口的前缀 /jboot/limitation是通过jboot.properties的jboot.limitation.webPath = /jboot/limitation进行设置的。

限流API安全设置

由于限流功能对系统至关重要,为了防止恶意用户猜出限流API对系统进行恶意操作,因此Jboot提供了限流API的权限设置功能,需要通过 通过jboot.properties的jboot.limitation.webAuthorizer = com.xxx.MyAuthorizer进行设置,其中MyAuthorizer需要实现io.jboot.web.limitation.web.Authorizer接口。

例如:

  1. public class MyAuthorizer implements Authorizer {
  2. @Override
  3. public boolean onAuthorize(Controller controller) {
  4. return true;
  5. }
  6. }

当限流API被请求的时候,会通过 MyAuthorizer 进行权限认证,只有MyAuthorizer通过(onAuthorize返回true)的时候,请求API才会生效。

websocket

在使用websocket之前,需要在jboot.properties文件上配置启动websocket,例如:

  1. jboot.web.websocketEnable = true
  2. jboot.web.websocketBufferPoolSize = 100

jboot.web.websocketBufferPoolSize 在没有配置的情况下,默认值是100

当做好以上配置后,就可以开始编写websocket的相关代码了。

html代码:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Insert title here</title>
  6. </head>
  7. <body>
  8. 服务器返回的信息:
  9. <input type="text" id="show"/>
  10. 浏览器发送的信息:
  11. <input type="text" id="msg"/>
  12. <input type="button" value="send" id="send" onclick="send()"/>
  13. <script>
  14. var ws = null ;
  15. var target="ws://localhost:8080/websocket/test";
  16. if ('WebSocket' in window) {
  17. ws = new WebSocket(target);
  18. } else if ('MozWebSocket' in window) {
  19. ws = new MozWebSocket(target);
  20. } else {
  21. alert('WebSocket is not supported by this browser.');
  22. }
  23. ws.onopen = function(obj){
  24. console.info('open') ;
  25. console.info(obj) ;
  26. } ;
  27. ws.onclose = function (obj) {
  28. console.info('close') ;
  29. console.info(obj) ;
  30. } ;
  31. ws.onmessage = function(obj){
  32. console.info(obj) ;
  33. document.getElementById('show').value=obj.data;
  34. } ;
  35. function send(){
  36. ws.send(document.getElementById('msg').value);
  37. }
  38. </script>
  39. </body>
  40. </html>

java代码:

  1. @ServerEndpoint("/websocket/test")
  2. public class Test {
  3. @OnOpen
  4. public void onOpen(){
  5. System.out.println("onOpen");
  6. }
  7. @OnClose
  8. public void onClose(){
  9. System.out.println("onClose");
  10. }
  11. @OnMessage
  12. public void onMessage(Session session,String msg){
  13. System.out.println("receive message : "+msg);
  14. if(session.isOpen()){
  15. try {
  16. //发送消息的html页面
  17. session.getBasicRemote().sendText(msg);
  18. } catch (IOException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }