JFinal+amazeUI+beetl+layer+shiro+poi+restful+handlebars初认识

时间:2021-11-02 15:27:27

刚入职公司,其用的框架为JFinal+amazeUI+beetl+layer+shiro+poi+restful+handlebars。这些一个都不熟悉,没办法,只能硬着头皮上。

经过一周的琢磨,初步搞明白了这些框架的使用场景和基本用法。

JFinal的横空出世,印证了那一句话:软件分为两种,一种设计简单,明显没有Bug;一种设计复杂,没有明显Bug。JFinal是一个轻量级的MVC、ORM、AOP框架,作为后端开发的微内核全方位扩展的新一代框架,其易用性和开发效率都是难以比肩的,其插件体系和Db+activeRecord架构也非常好。它主要由JFinalFilter、JFinalConfig、Handler、Interceptor、Controller、Render、Plugin等部分构成。每个部分都是基于接口实现的,支持完整的自定义,使用灵活,扩展性强。

JFinal+amazeUI+beetl+layer+shiro+poi+restful+handlebars初认识

其基本运行原理:

从一次新增页面的add操作请求来看:

1. 首先是客户端点击新增按钮,提交一个新增请求,请求中会带上服务端处理地址url

2. 所有请求都会被JFinalFilter拦截,然后调用Handler进行详细处理

3. Handler是一个链条形式的调用,包括0-n个自定义Handler,以及最后一个ActionHandler,依次执行,当然可以自定义跳出。

4. 进入ActionHandler后,首先会根据请求的target从缓存的ActionMapping中映射获取到具体操作对应的Action对象,这个对象里面封装了方法名、方法上面的拦截器,方法所在的Controllercontroller上面的拦截器等。然后根据Action以及Controller实例构造出ActionInvocation。

5. 接下来通过ActionInvocation的invoke进行具体处理,这是一个明显的Command模式的实现。首先是拦截器的调用,拦截器调用中会重新回调ActionInvocation的invoke,当拦截器调用完毕后,会调用当前操作的method

6. 当进入具体controller的新增方法add时,调用基类的getModel(Systemparam.class);这个方法会从request中解析出所需要的数据,通过反射设置给具体的Model实体类,最终通过ActiveRecord来进行数据存储

7. 最后是页面渲染rerender


Controller的主要作用是从Request中获取参数,往Request和Session中放入属性、Cookie,使用ModelInjector的inject方法从Request中获取model{网页中的格式为user.name,user.age},该方法有两个分支,如果model继承于Model则调用set(attrName,attrValue),否则就是普通的JavaBean,调用setAttrName(attr)。

Controller调用cos框架获取上传的文件,封装成MultiPartRequest(继承于HttpServletRequestWrapper),该类可以获取文件或者参数。


Handler是处理器,构成责任链,因为在handle方法中获取到了Request和Response,所以它具备对请求的完全控制权。在JFinalFilter的init方法中,构建了这个链条,最后一个handler是ActionHandler,并获取到第一个handler,其配置由JFinalConfig的configHandler完成。


Action就代表Controller中的一个方法,包括Controller这个Class,controllerKey,actionKey,Method,methodName,interceptors,viewPath,是一个简单的只读JavaBean。

ActionHandler中的handle方法中调用controller的init方法,通过Invocation调用controller之中的Interceptor,然后调用controller的getRender获取Render,之后判断是不是ActionRender,避免调用相同的controller的相同方法造成死循环。因为在一般的controller的方法中都会调用controller中的render()方法系列,将controller中的render引用为某render,故在ActionHandler的handler方法最后调用render.render()就会调用不同render的render方法。在配置Constans的时候调动setMainRenderFactory,就能设置程序的主Render工厂,以后获取的就是这个工厂生产的Render,这就是典型的工厂方法模式。


Render代表渲染器,拥有request和response的引用,所以render()方法中就能作不同的处理,例如FileRender就会调用response的setHeader(“Content-disposition”,“attachment;filename=...”)等以实现文件下载;BeetlRender就会调用加载模板和绑定属性哪些方法(见以下分析)。


关于自动扫描classpath和jar包中的AutoBindRoutes的机制:

首先JFinalConfig中的configRoute(Routes me)方法中调用me.add(new AutoBindRoutes());其源码为

public Routes add(Routes routes) {
if (routes != null) {
routes.config();// very important!!!
map.putAll(routes.map);
viewPathMap.putAll(routes.viewPathMap);
}
return this;
}

使得Routes的map和viewPathMap中添加了AutoBindRoutes的map和viewPathMap,这个方法中,调用了routes.config方法,这个方法就要求你自己配置你的map和viewPathMap,这个是典型的模板方法模式。AutoBindRoutes继承于Routes,其config方法中,

public void config()
 {
     List controllerClasses = ClassSearcher.of(Controller.class).includeAllJarsInLib(this.includeAllJarsInLib).injars(this.includeJars).search();
     ControllerBind
controllerBind = null;
     for (Class controller : controllerClasses)
       if (!this.excludeClasses.contains(controller))
      {
        controllerBind = (ControllerBind)controller.getAnnotation(ControllerBind.class);
        if (controllerBind == null) {
          if (this.autoScan)
         {
            add(controllerKey(controller), controller);
          } } else if (StrKit.isBlank(controllerBind.viewPath())) {
           add(controllerBind.controllerKey(), controller);
         } else {
           add(controllerBind.controllerKey(), controller, controllerBind.viewPath());
        }
      }
 }

首先委托ClassSearcher去ClassPath和Jar包中去扫描Controller的子类(要求以Controller结尾,是COC原则),然后检查该类上是否含有ControllerBind(强制修改路径的)注解,有的话就按照ControllerBind的路径执行,否则按照默认的规则执行。ClassSearcher.search方法就自己想吧,代码不是很复杂。

至此,AutoBindRoutes中含有了Controller的信息,在JFinalFilter的init方法中

if (jfinal.init(jfinalConfig, filterConfig.getServletContext()) == false)
throw new RuntimeException("JFinal init error!");

init方法里面

boolean init(JFinalConfig jfinalConfig, ServletContext servletContext) {
this.servletContext = servletContext;
this.contextPath = servletContext.getContextPath();

initPathUtil();

Config.configJFinal(jfinalConfig);// start plugin and init logger factory in this method
constants = Config.getConstants();

initActionMapping();
initHandler();
initRender();
initOreillyCos();
initTokenManager();

return true;
}

configJFinal方法中

static void configJFinal(JFinalConfig jfinalConfig) {
jfinalConfig.configConstant(constants);initLoggerFactory();
jfinalConfig.configRoute(routes);
jfinalConfig.configPlugin(plugins);startPlugins();// very important!!!
jfinalConfig.configInterceptor(interceptors);
jfinalConfig.configHandler(handlers);
}

这个方法中调用JFinalConfig的configRoutes就会调用前面说的那些方法。此时Config类中就保留了Routes信息。

initActionMapping方法中

private void initActionMapping() {
actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors());
actionMapping.buildActionMapping();
}

就用Config的getRoutes获取Routes构造出actionMapping,然后调用buildActionMapping

void buildActionMapping() {
mapping.clear();
Set<String> excludedMethodName = buildExcludedMethodName();
ActionInterceptorBuilder interceptorBuilder = new ActionInterceptorBuilder();
Interceptor[] globalInters = interceptors.getGlobalActionInterceptor();
interceptorBuilder.addToInterceptorsMap(globalInters);
for (Entry<String, Class<? extends Controller>> entry : routes.getEntrySet()) {
Class<? extends Controller> controllerClass = entry.getValue();
Interceptor[] controllerInters = interceptorBuilder.buildControllerInterceptors(controllerClass);

boolean sonOfController = (controllerClass.getSuperclass() == Controller.class);
Method[] methods = (sonOfController ? controllerClass.getDeclaredMethods() : controllerClass.getMethods());
for (Method method : methods) {
String methodName = method.getName();
if (excludedMethodName.contains(methodName) || method.getParameterTypes().length != 0)
continue ;
if (sonOfController && !Modifier.isPublic(method.getModifiers()))
continue ;

Interceptor[] methodInters = interceptorBuilder.buildMethodInterceptors(method);
Interceptor[] actionInters = interceptorBuilder.buildActionInterceptors(globalInters, controllerInters, methodInters, method);
String controllerKey = entry.getKey();

ActionKey ak = method.getAnnotation(ActionKey.class);
String actionKey;
if (ak != null) {
actionKey = ak.value().trim();
if ("".equals(actionKey))
throw new IllegalArgumentException(controllerClass.getName() + "." + methodName + "(): The argument of ActionKey can not be blank.");

if (!actionKey.startsWith(SLASH))
actionKey = SLASH + actionKey;
}
else if (methodName.equals("index")) {
actionKey = controllerKey;
}
else {
actionKey = controllerKey.equals(SLASH) ? SLASH + methodName : controllerKey + SLASH + methodName;
}

Action action = new Action(controllerKey, actionKey, controllerClass, method, methodName, actionInters, routes.getViewPath(controllerKey));
if (mapping.put(actionKey, action) != null)
throw new RuntimeException(buildMsg(actionKey, controllerClass, method));
}
}

// support url = controllerKey + urlParas with "/" of controllerKey
Action actoin = mapping.get("/");
if (actoin != null)
mapping.put("", actoin);
}

这里面就涉及Before、Clear、ActionKey的执行逻辑(略)。


AmazeUI是一个前端框架,主要提供一些组件和布局。

beetl是新一代Java模板引擎,在后端执行,通过ResoureLoader和Configeration获取模板,编译后通过绑定数据渲染出需要的页面。与JFinal的整合就是通过注册一个BeetlRenderFactory(me.setMainRenderFactory(new BeetlRenderFactory());),如此在渲染的时候就生成BeetlRender进行渲染,当然也会执行beetl的一些步骤。通过看源码知道,BeetlRenderFactory.getRender方法返回一个BeetlRender,他继承于Render,在render方法中

this.response.setContentType(contentType);
WebRender webRender = new WebRender(this.gt);
webRender.render(this.view, this.request, this.response, new Object[0]);

这个render方法中

Enumeration attrs = request.getAttributeNames();
while (attrs.hasMoreElements())
 {
String attrName = (String)attrs.nextElement();
template.binding(attrName, request.getAttribute(attrName));
}

WebVariable webVariable = new WebVariable();
webVariable.setRequest(request);
webVariable.setResponse(response);
template.binding("session", new SessionWrapper(request.getSession(false)));

template.binding("servlet", webVariable);
template.binding("request", request);
template.binding("ctxPath", request.getContextPath());

这就是在页面中能够使用request中的数据的奥秘。


layer是一个效果很好的弹出层。


shiro是一个可以认证、授权、加密、会话管理、与Web集成、缓存的Java安全框架。目前还没研究透彻。


poi用于生成Excel表格。


restful主要是控制代码风格的。


handlebars是一个网页前端js的模板技术,在浏览器view source的时候还能看见其特有的语法{{}},说明是在浏览器中执行的。基本的使用步骤是

1:用script标签放入模板代码:

<script id="entry-template" type="text/x-handlebars-template"> template content </script>
2:编译模板
var source   = $("#entry-template").html();
var template = Handlebars.compile(source);

3:引入数据,当然这个数据的来源就不定了,可以是本地的,也可以是服务器来的

var context = {title: "My New Post", body: "This is my first post!"}
var html    = template(context);

得到下面的HTML

<div class="entry">
  <h1>My New Post</h1>
  <div class="body">
    This is my first post!
  </div>
</div>
还可以通过注册函数实现自己的逻辑

Handlebars.registerHelper('list', function(items, options) {
  var out = "<ul>";

  for(var i=0, l=items.length; i<l; i++) {
    out = out + "<li>" + options.fn(items[i]) + "</li>";
  }

  return out + "</ul>";
});
{{#list people}}{{firstName}} {{lastName}}{{/list}}

使用一下数据

{
  people: [
    {firstName: "Yehuda", lastName: "Katz"},
    {firstName: "Carl", lastName: "Lerche"},
    {firstName: "Alan", lastName: "Johnson"}
  ]
}

就能渲染出以下效果

<ul>
  <li>Yehuda Katz</li>
  <li>Carl Lerche</li>
  <li>Alan Johnson</li>
</ul>

以上是对这些框架的一个初步认识,随着时间的推移和我的坚持,应该能啃下他们。