轻量级Java应用开发框架“ZolltyMVC”交流
2015年01月16日

ZolltyMVC框架简介


ZolltyMVC是基于 Java 语言的极速 WEB 框架,核心设计目标是开发迅速、代码量少、学习简单、轻量级、易扩展。


它在传统MVC框架的基础上做了大量优化和创新设计,以适应软件开发的个性化需求,提升了系统性能,具有更好的可扩展性


如同Spring、StrutsMVC、JFinal、Argo(58同城自主开发的框架)一样,ZolltyMVC也完全开源,在Github、Google Code等都能找到。


ZolltyMVC自从2013年发布第一个稳定版之后,目前已经更新到了 1.3.0.RELEASE 版本,在多个项目中运行良好。


经过长时间的运作与运行,证明ZolltyMVC是一个可靠的高效的web框架。


示例代码  a controller demo


@Controller

@CBefore({PermissionCheck.class}) // before controller method execution

public class HelloWorldController {


     // 属性注入,支持按类型注入

    @Inject

    private DiService diService;


    @RequestMapping("/lesson1/hello-jsp")

    public View helloJsp() {


        // Return a JSP View

        return new JspView("/lesson1/hello.jsp");

    }


    @RequestMapping("POST:/user/{userName}") // Only allow POSTmethod

    public View helloSomeOne(@URIParam("userName") String userName) {


        // Get userName from URI

       return new JsonView("{\"name\": \""+userName+"\"}");

    }


}


ZolltyMVC功能和特点


1. Web层:它是一个通用纯Servlet请求控制转发器RESTful URL 路由,代码简洁、效率非常高),基于原生RESTful设计,且支持各种定制化URL方案。扩展功能支持拦截器配置,支持ModelDriven(视图层VO自动封装),支持多视图模板(Jsp View、JSON View等)。


2. Bean:它是一个轻量级IOC/DI框架,可以独立应用于Standard Java,强大的支持各种形式加载Bean,强大的可扩展性和可集成性。(只支持单实例,功能比Spring的要精简很多,但是一般够用了)


3. 对于中小型项目,它完全可以替代SpringMVCSpring+Struts,已经经过多个企业级生产项目的考验,高效稳定运行于Tomcat、Jetty、WebSphere、JBOSS服务器


4. 小巧,代码量少。只有几百kb,比Spring要精简很多,而且常用功能一应俱全并增加了一些nice的功能


与Spring框架功能的对比


下图是Spring框架的主要功能:

spring-framework.png

ZolltyMVC去掉了其中的ORMJEE的部分,并且将AOPWeb合并,只支持有限的AOP


JavaEE中的两个MVC模型


MVC Model 1

  即传统的JSP+JavaBean,目前已经很少用了。


MVC Model 2

  该模型的标准是:

  JSP(视图View,用户界面

 +Servlet(控制器Controller,输入处理

 +JavaBean(模型Model,功能逻辑)。

  由于Java EE中的JavaBean(EJB2.0)太复杂了,所以该模型最流行的一个版本是SSH,即Struts+Spring+Hibernate。


控制反转(IOC )

public class PersonService {

     private PersonDao pDao = new PersonDaoImpl();

     public void save(Person person){

         pDao.save(person);

     }

}


PersonDao 是在应用内部创建及维护的。所谓控制反转就是应用本身不负责依赖对象的创建及维护,依赖对象的创建及维护是由外部容器负责的。这样控制权就由应用转移到了外部容器,控制权的转移就是所谓反转。


依赖注入 DI ( Dependency Injection )


当我们把依赖对象交给外部容器负责创建,那么PersonServiceBean 类可以改成如下:

public class PersonService {

  private PersonDao personDao ;


  //通过构造器函数,让容器把创建好的依赖对象注入进来

  //也可以使用setter方法进行注入

  public PersonService(PersonDao personDao){

      this.personDao=personDao;

  } 


  public void save(Person person){

     personDao.save(person);

  }

}


所谓依赖注入就是指:在运行期,由外部容器动态地将依赖对象注入到组件中


ZolltyMVC注入依赖对象


1、基本类型对象注入:

<bean id="stu2" class="com.travelsky.Student">

    //属性setter方法注入

   <property name="name" value="" />

</bean>


2、注入其他bean:

<bean id="stu2" class="com.travelsky.Student">

    //属性setter方法注入

   <property name="stuDao" ref="stuDao" />

</bean>


<bean id="stuDao" class="com.tavsky.StuDao" />




依赖注入-自动装配


在Java代码中使用@Autowired或@Resource注解方式进行装配,这两个注解的区别是:@Autowired 默认按类型装配,@Resource默认按名称装配,当找不到与名称匹配的bean才会按类型装配。

  @Autowired

  private PersonDao  personDao;//用于字段上


    @Autowired注解是按类型装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它required属性为false


    @Resource注解和@Autowired一样,也可以标注在字段或属性的setter方法上,但它默认按名称装配。名称可以通过@Resourcename属性指定,如果没有指定name属性,当注解标注在字段上,即默认取字段的名称作为bean名称寻找依赖对象,当注解标注在属性的setter方法上,即默认取属性名作为bean名称寻找依赖对象。


   @Resource(name=“personDaoImpl”)

    private PersonDao  personDao;//用于字段上


ZolltyMVC中的用法如下:


   @Inject(“personDaoImpl”)

    private PersonDao personDao; //用于字段上



RESTful URI路由


1、URI路由

    基本实现方式是:Servlet控制转发(超级Servlet)


2、RESTful 架构风格


    REST,即表述性状态转移(Representational State Transfer)


要深入理解REST,需要理解REST的五个关键词:

资源(Resource)

资源的表述(Representation)

状态转移(State Transfer)

统一接口(Uniform Interface)

超文本驱动(Hypertext Driven)


资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。

资源的表述可以有多种格式,例如HTML/XML/JSON/图片/视频/音频等等。

统一接口,例如HTTP/1.1协议。

7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS


ZolltyMVC支持原生RESTful,URL风格更简洁、优雅,比如:

http://www.oschina.net/code

http://www.oschina.net/blog

http://www.oschina.net/code/tag/jquery

http://www.oschina.net/code/list?lang=java



对应ZolltyMVC的后端Controller代码:


   // RESTful风格URL路由-限定为POST方式


   @RequestMapping("POST:/admin/login")

   public View login(@HttpParam("loginvo") LoginVO loginvo){

           // ……省略

           return new JspView("/admin/main.jsp"); // 返回到JSP视图层

   }


URI路由与SpringMVC的区别


ZolltyMVC的控制器,是基于原生Servlet进行设计的。有类似于SpringMVC的地方,但是效率比SpringMVC要高很多!


比如,因为SpringMVC要支持多文件上传,其逻辑就比ZolltyMVC的HTTP请求处理逻辑要复杂许多了。我对比了一下两者的算法复杂度,SpringMVC的算法复杂度至少是ZolltyMVC的n*10^2倍。


而且还有一点,SpringMVC采用的是AntPathMatcher的URL路径匹配算法,ZolltyMVC采用的是自己写的一个简洁的递归算法,实测它的匹配效率是ANT的几十倍参见org.zollty.util.match.ZolltyPathMatcher.java, 及其单元测试用例


另外,URI路由支持一些高级的“通配”功能。

例如:

   @RequestMapping("GET:/user/{userName}")

    public View hello(@URIParam("userName") String uName) {

      

        return new TextView("Hello "+ uName);

    }

这个“{}”号可以通配一个URI参数,而中括号“[]”可以通配多个“/”间隔的URI


视图模型


页面视图(View)


   视图可以是JSP、HTML、XML、模板、静态文件等等,甚至可以扩展自定义的View

   例如我写了个ResourceView,读取jar包中META-INF目录下的html页面)。这样做会非常方便,编程更为灵活。

 

支持返回的类型包括(但不限于)如下类型:

JspView

HtmlView

TextView

JsonView


视图用法举例:

   @RequestMapping("/admin/login")

   public View login(){


          // 返回到JSP 视图层

          return new JspView("/admin/main.jsp");

   }


ModelDriven - 视图层参数自动封装


举个例子说明:


    @RequestMapping("/lesson1/hello-catvo")

    public View hello(

        @HttpParam("cat") CatVO cat) {


        // 自动封装一个标准Bean对象

       

        // 返回一个页面视图

        return new TextView(

        "Hello " + cat.getName() + ", Age: " + cat.getAge());

    }


提示: @HttpParam不仅可以接收 String 类型的参数,还可以接收对象参数哦! 比如 UserVO ,它会自动把里面的值封装

   

访问URL示例: http://127.0.0.1:8080/zollty-mvc-demo/lesson1/hello-catvo?name=Kitty&age=27



面向切面编程(AOP)简介


  AOP(Aspect Oriented Programming),也就是面向切面编程,作为面向对象编程的一种补充,专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理安全检查缓存、对象池管理等。常见AOP实现的途经是通过 AOP 框架自动创建的AOP代理(主要分为静态代理和动态代理)


举个例子:

  有这样一个需求,对每个Controller方法记录其执行耗费的时间


   @RequestMapping("POST:/admin/login")

   public View login(@HttpParam("loginvo") LoginVO loginvo){

           // ……省略

           return new JspView("/admin/main.jsp");

   }



AOP和拦截器的类型


按执行顺序分:

在Controller的方法

    执行之前MvcBefore

    执行之后、渲染之前MvcBeforeRenderMvcAfterThrow

    执行之后MvcAfter

    执行前后MvcAround


拦截器按功能分为两类: 1)通用拦截器  2)业务拦截器


拦截器按作用范围大小分为三类(在三个不同地方定义的拦截器):

    1)通用拦截器:在AOP类上用 @AOPMapping({"/admin/*"}) 定义的拦截器,作用范围为所有匹配的URI对应的controller method。


    2)Controller拦截器:基于特定Controller来定义Controller层面的拦截器。在Controller类上用 如@CBefore({HxxxBefore.class})标注 的拦截器。


  3)Controller Method拦截器基于某个Controller的特定Method来定义方法层面的拦截器。在Controller类的@RequestMapping方法上用 如@CBefore({HxxxBefore.class})标注 的拦截器。


基于注解的AOP拦截器之 MvcBefore


MvcBefore 在执行 Controller Method 之前执行。


业务场景

》Step 1. 权限检查检查session是否过期。过期则直接返回错误视图。

》Step 2. 权限检查:检查是否有跨站点脚本攻击的非法参数,如果有则返回错误视图。

》Step 3. 预处理读取请求信息、Cookie等,并做一些解析后存入请求对象中,方便后续流程使用。

》Step 4. 日志记录记录请求信息。 可以做成异步处理。


可以有多个MvcBefore 与Controller的方法相关联。按照先后顺序执行这些MvcBefore拦截器。(Controller层面的拦截器,其执行顺序要先于Method层面的拦截器。通用拦截器,理应最先执行,然后才执行业务拦截器。)

执行顺序:通用拦截器、Controller拦截器、ControllerMethod拦截器。在每一级别上都是按从小到大先后顺序执行。


错误处理如果MvcBefore执行出错,可以返回一个View,MVC框架会提交这个View,终止后续执行(后面的MvcBefore等都不会执行了)。 如果MvcBefore抛出了未知异常,框架会catch异常并打印日志,并返回错误视图,终止后续执行。


基于注解的AOP拦截器之 MvcAround


MvcAround 在 执行Controller Method的前后 执行(把Controller Method包裹在MvcAround之中执行)。


业务场景

》Step 1. 性能监控记录处理时间,如果超时则打印log或者发送邮件。

》Step 2. 开关:如OpenSessionInView,在进入处理器打开Session,在完成后关闭Session


定义了MvcAround的Controller的method,不会直接执行Controller的method,而是会调用MvcAround的方法, 在MvcAround的方法中再去调用Controller的method。 如果有多个MvcAround,则递归调用, 执行顺序:通用拦截器、Controller拦截器、ControllerMethod拦截器。在每一级别上都是按从小到大先后顺序执行。


错误处理如果MvcAround返回View则终止程序执行。 如果抛出了未知异常,框架会catch异常,如果定义了MvcAfterThrow则交由MvcAfterThrow进行处理,否则直接返回错误视图。


依赖注入(DI)高级用法


用方法注解 注入对象:

@Inject

public void setUser(User user) {

    this.user = user;

}

还比如支持复合对象

<bean id="user2" class="com.test.User >

       <property name="name" value="zollty" />

       <property name="age" value="26" />

       <property name="students" >

              <list>

                     <value>jack</value>

                     <value>lily</value>

              </list>

       </property>

</bean>


还比如支持从方法中获取一个对象实例:

<bean id="dataSource" class="org.test.JndiCreator#getDataSource" >

       <property name="jndiName" value="jdbc/web"/>

</bean>


使用JdbcTemplate进行数据库操作


@Service

public class PersonServiceImpl  implements  PersonService  {

    private JdbcTemplate jdbcTemplate;

    @Resource

    public void setDataSource(DataSource dataSource) {

         this.jdbcTemplate = new JdbcTemplate(dataSource);

    }

    public Person getPerson(Integer id){

        RowMapper rowMapper = new RowMapper() {

            public Object mapRow(ResultSet rs, int rowNum) throws SQLException {

                Person person = new Person();

                person.setId(rs.getInt("id"));

                person.setName(rs.getString("name"));

                return person;

             }

         };

        return (Person) jdbcTemplate.queryForObject(

                  "select * from person where id=?",  new Object[]{id}, 

                  new int[]{java.sql.Types.INTEGER},  rowMapper);

     }


高级选项 配置


在classpath目录下,新建一个zollty-mvc.xml,内容类似下面:


    //指定class的扫描路径(用于识别注解配置),支持多个配置,支持扫描jar包

    <component-scan base-package="com.zollty.mvcdemo" />

 

    //指定视图的路径的前缀及编码,可省略该配置,默认如下

    <mvc view-path="/WEB-INF/views" view-encoding="UTF-8" />


    //配置 排除拦截的URI 的前缀和后缀

   <no-intercept prefix="/resources/,/pages/" suffix=".html,.js,.css"/>

 

    //绑定外部的日志处理器(可以自定义),可省略该配置,默认为org.zollty.log.ConsoleLogger

   <logger class="org.zollty.log.Log4jLogger" level="TRACE" />

 

    //使用自定义的error页面,【可省略该配置,默认ZolltyMVC自动生成

    <errorPage path="/error.jsp" />


    <!-- 引入其他配置文件,【支持多个配置】 -->

    <import resource="classpath:external-beans.xml" />


Web部署配置


对于Servlet 2.x以下版本的Web容器,需要配置web.xml,内容如下:


<listener>

       <listener-class>org.zollty.framework.mvc.ContextLoaderListener</listener-class>

</listener>

<servlet>

       <servlet-name>zolltyMVC</servlet-name>

       <servlet-class>org.zollty.framework.mvc.DispatcherServlet</servlet-class>

       <load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

       <servlet-name>zolltyMVC</servlet-name>

       <url-pattern>/app/*</url-pattern>

</servlet-mapping>


Thanks!   Q&A