本系列博文,将会一步一步介绍如何构建一个轻量级的web框架<br/>jbeer git地址:
JBEER的MVC模块采取完全注解的Restful风格。支持Controller的单例和非单例模式,开发人员可以通过配置来选择。
一、启动MVC
关于MVC的配置在Configurate接口的public void configurateWeb(WebConfig config);方法内配置WebConfig类来实现。该类提供的几个设置方法如下:
setViewPrefix(String viewPrefix)
设置视图的前缀,比如/WEB-INF/pages
setWebTempFileDir(String webTempFileDir)
设置文件上次的临时目录,默认是/temp,即,应用根目录下面的temp目录。setViewSuffix(String viewSuffix)
视图的后缀,例如:.jsp后者.ftlisSingletonMode(boolean singletonMode)controller
是否单例模式,默认是非单例模式,如果是单例模式,框架将不会自动将request中的参数,自动注入到controler的字段中(如果是对象,将会自动封装到对象的字段中,例如user.name,在controller中有user这个User
字段,那么将会初始化一个User
对象并且复制name到User
的name属性上面)
通过上面描述我如下配置了MVC模块 #######1)配置WEB.XML####### <!-- lang: xml --> <context-param> <param-name>basePackageName</param-name> <param-value>org.jbeer.sample</param-value> </context-param> <listener> <listener-class>com.jbeer.framework.startup.JBeerWebContextListener</listener-class> </listener> <servlet> <servlet-name>jbeerDispatcherServlet</servlet-name> <servlet-class>com.jbeer.framework.web.JBeerDispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>jbeerDispatcherServlet</servlet-name> <url-pattern>*.htm</url-pattern> </servlet-mapping>
1、配置JBeerDispatcherServlet
用于拦截MVC的请求,如上是拦截素有.htm的请求<br/> 2、配置JBeerWebContextListener
用于启动框架,加载应用相关信息<br/> 3、配置basePackageName
是告知框架,需要扫描类的基础包,该包下面的所有类都会进行扫描 #######2)配置应用的MVC####### <!-- lang: java --> public class AppConfig implements Configurate { @Override public void configurateWeb(WebConfig config) { config.setViewPrefix("/WEB-INF/pages"); config.setViewSuffix(".jsp"); config.isSingletonMode(true); } } 由于上面配置了basePackageName
,并且AppConfig
是在配置的包(子包)下,那么框架将会扫描到AppConfig
类,并执行configurateWeb方法,对MVC进行配置。这里的配置告知了框架视图模板是WEB-INF/pages下面的.jsp文件,同时告知框架MVC里面的Controller是单例模式,那么需要手动在Controller中获取请求参数。下面便是一个单例模式下面的Controller实现 #######3)实现FirstController####### <!-- lang: java --> @Controller(urlPattern="/first") public class FirstController extends BaseController{
@RefBeanprivate UserService userService;@Action(urlPatterns="post.htm",requestType=RequestType.POST)public ModelAndView post() throws JBeerException{ System.out.println(this); PageModelAndView mav = ModelAndView.createModelAndView(); Listuser = getList("user", User.class); List file = getFiles("file"); Short test = getShort("test"); User[] users = getArray(User.class, "user"); int a = getInteger("a"); mav.setDataMap("test", test); mav.setDataMap("user", users[0]); mav.setDataMap("file", file.get(0).getName()); mav.setView("view"); return mav;}@Action(urlPatterns="index.htm",requestType=RequestType.ANY)public String index() throws IOException, ScanClassException{ return "index";}}
从上往下看,分析该类的实现。<br/> 1、通过注解@Controller
告知框架FirstController
是一个控制器,并且匹配的路径是以${ctx}/first
开始<br> 2、FirstController
继承了BaseController
,在BaseController
中,有获取HttpServletRequest
中的参数方法,例如getList
,getFiles
,getShort
,getObject
等等,继承了该类,那么不用再去手动从HttpServletRequest
获取请求参数。当然,也可以不用继承该类,通过RequestParameterUtil
里面的静态方法一样可以达到同样的效果,具体可以自己选择。<br/> 3、方法post通过注解@Action
进行了修饰,表示该方法将会响应来自路径${ctx}/first/post.htm
,并且是POST方式的请求,通过注解@Action
注解进行修饰的方法,框架都没当作是一个Action来进行解析。<br/> 4、action方法返回的是ModelAndView
,该类是Action返回的一个抽象,该类不能直接实体化,需要通过ModelAndView
下面的静态方法构造对应的视图和数据模型对象。如果是需要返回一个页面到前端,则是通过createModelAndView()
来构造一个PageModelAndView
对象,如果是需要返回一个JSON对象返回到前端,那么通过createJsonModel
来构造一个JSONModelAndView
等等....根据具体需求可以生成对应的视图和数据模型<br>
通过上面几个步骤,JBeer的MVC就可以正常执行了,可以处理前端发起的请求。下面对JBeer里面视图渲染的思想进行描述一下。 #二、JBeer里面的视图渲染#
渲染视图,包括三个核心组件:数据,渲染器,视图。 数据和视图,是具体的请求在Action的方法中告知框架的,而渲染器,则需要框架根据对应的数据模型来匹配对应的渲染器。所以JBEER中,为每个ModelAndView
匹配了视图。 ######1)数据模型和视图的抽象######
public class ModelAndView {protected String view;protected String callback;protected Mapdatas = new HashMap ();protected ViewType type = ViewType.PAGE;protected Object data = null;protected String content;protected ModelAndView(){ }public static JSONModelAndView createJsonModel(){ return new JSONModelAndView();}public static JSONPModelAndView createJsonpModel(){ return new JSONPModelAndView();}public static PageModelAndView createModelAndView(){ return new PageModelAndView();}public static AJAXModelAdnView createAJAXModelAdnView(){ return new AJAXModelAdnView();}protected ModelAndView setContent(String content){ this.content = content; return this;}protected ModelAndView setView(String view){ this.view = view.startsWith("/")?view:("/"+view); return this;}protected ModelAndView setCallback(String callback){ this.callback = callback; return this;}protected ModelAndView setViewType(ViewType type){ this.type = type; return this;}protected ModelAndView setDataMap(String key,Object data){ datas.put(key, data); return this;}protected ModelAndView setData(Object data){ this.data = data; return this;}public String getContent(){ return content;}public String getView(){ return view;}public Map getDatas(){ return datas;}public Object getData(){ return data;}public String getCallback(){ return callback;}public ViewType getViewType(){ return type;}public static class JSONModelAndView extends ModelAndView{ public JSONModelAndView(){ this.type = ViewType.JSON; } @Override public JSONModelAndView setData(Object data) { this.data = data; return this; } }public static class JSONPModelAndView extends JSONModelAndView{ public JSONPModelAndView(){ this.type = ViewType.JSONP; } @Override public JSONPModelAndView setCallback(String callback) { this.callback = callback; return this; }}public static class AJAXModelAdnView extends ModelAndView{ public AJAXModelAdnView(){ this.type = ViewType.AJAX; } @Override public ModelAndView setContent(String content) { this.content = content; return this; } }public static class PageModelAndView extends ModelAndView{ public PageModelAndView(){ this.type = ViewType.PAGE; } @Override public PageModelAndView setView(String view) { this.view = view; return this; } @Override public PageModelAndView setDataMap(String key, Object data) { this.datas.put(key, data); return this; } }public static enum ViewType{ JSON("json"),PAGE("page"),FILE("file"),JSONP("jsonp"),AJAX("ajax"); private String name; private ViewType(String name){ this.name = name; } public String getName(){ return name; } }}
在类ModelAndView
中抽象出了描述数据和视图之间关系公共属性,并且相关的set方法是protected,那么在外部直接通过ModelAndView
是无法进行设置值的,而ModelAndView
有几个子类,分别是:JSONModelAndView
,JSONPModelAndView
,PageModelAndView
,AJAXModelAdnView
,那么各个子类,则对应了一种类型的视图了,并且在各个子类中通过基础父类相关set方法,并调整为public,以达到ModelAndView
中的各个属性的set方法按需分配到了各个子类中,并且各个子类根据具体需求,对外暴露相关的set方法。这样就达到了绑定数据和相应视图关系。 ######2)视图渲染器###### 通过抽象一个渲染器的公共接口,Render
来规范各种渲染器。 <!-- lang: java --> public interface Render {
/** * *函数功能说明:当一个容器中有多个render的时候,order的值高的,将会优先调用其来进行渲染
*Bieber 2014年6月17日
*修改者名字 修改日期
*修改内容 * @return int */public int order();/** * *
函数功能说明:是否支持当前数据模型和视图渲染
*Bieber 2014年6月13日
*修改者名字 修改日期
*修改内容 * @return boolean */public boolean isSupport(ModelAndView modelView);/** * *
函数功能说明:进行渲染操作
*Bieber 2014年6月13日
*修改者名字 修改日期
*修改内容 * @return void */public void render(HttpServletRequest request, HttpServletResponse response, ModelAndView modelView) throws RenderingViewException;}
该接口中只定义了三个方法,order
方法用于当支持某一类型的视图有多个渲染器,那么会调用该值最高的渲染器渲染视图。isSupport
用于判断当前渲染器是否支持当前数据模型的渲染,render
方法则是进入到渲染具体方法,该方法执行完毕后,则将渲染完毕的视图,返回到前端。基于这个接口,框架默认提供了:AJAXRender
用于渲染AJAXModelAdnView
的数据模型和视图,JSONPRender
则对一个了渲染JSONPModelAndView
等等,这里就不再赘述,具体可以查看JBEER框架具体实现。<br/> 基于Render
的接口,以及JBeer插件接口,可以提供freemark(具体见 模块)和velocity(具体见 模块)的支持,以及对文件下载的支持,文件下载也可以理解为是一种试图渲染器,只是返回前端的是字节流(具体见 模块)。通过这种方式可以更加灵活的扩展JBeer的视图类型以及渲染器。
后续会对MVC部分细节的实现,进行详细描述,有什么问题欢迎大家积极提问。