MVC框架Wheel简单实例

这个教程,主要就是介绍如何使用MVC框架Wheel.简单的描述下我们想要做的事情,创建一个user表,然后通过不同的视图方式显示出user表的内容。我们采用Mysql数据库.

数据库

DROP TABLE IF EXISTS `user_`;
CREATE TABLE `user_` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(45) NOT NULL,
  `CREATE_DATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `MODIFY_DATE_TIME` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `CREATE_USER_ID` int(10) NOT NULL,
  `MODIFY_USER_ID` int(10) NOT NULL,
  `DELETED` int(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

INSERT INTO `user_` VALUES (1,'wheel','2012-07-20 07:03:20','0000-00-00 00:00:00',1,1,0),(2,'asmsupport','2012-07-20 07:03:20','0000-00-00 00:00:00',1,1,0);

web.xml

web.xml配置非常简单,加入一个Servlet就可以了。
    <servlet>
        <servlet-name>jwmvc</servlet-name>
        <servlet-class>cn.wensiqun.wheel.mvc.dispatcher.ActionServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>jwmvc</servlet-name>
        <url-pattern>*.*</url-pattern>
    </servlet-mapping>

global.properties

这个文件是Wheel的一个配置文件,具体有哪些属性可以查看源码中根路径下的"global.default.properties".这里我们只做其中几项配置
#数据库配置信息
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=mysql

##########################################################
# 是否是在运行时生成类,默认是true,当选择false的时候,需要使用
# wheel的maven插件wheel-maven-plugin,这个插件的作用就是将生成
# 的类添加到项目war包中,并且替换原有的class,使用这种方式的好处
# 就是减少系统的开销
##########################################################
generator.class.runtime=true

##########################################################
# 将我们生成的class存入到指定路径,如果没有设置这个属性的值,将
# 不会输出生产的类。输出我们生成的class可以便于用反编译工具查看
# 对应的源码,这样可以更直观的清楚wheel的原理。当然也可能反编译
# 出来的源码内容不正确,具体原因可以参考
# http://www.wensiqun.com/2013/06/09/asmsupport_tutorial_3.html
##########################################################
generator.class.output=D:/TEMP/mock_generated

##########################################################
# Action类搜索路径,wheel会在这个路径里面查找被Action注解过的Class
# 生成代理类,和SpringMVC中的
#
# 类似。
# 这个路径也是我们存放ResultSetConverter的路径。
##########################################################
scan.base.path=cn.wensiqun.,jw.

##########################################################
# Dao层接口的实现类的匹配前缀,比如接口com.wensiqun.wheel.TestDao
# 如果我们设置
#  dao.impl.class.suffix=impl.
#  dao.impl.class.suffix=JDBC 
# 那么我们将去寻找的实现类是com.wensiqun.wheel.impl.TestDaoJDBC, 
# 通过如下公式可以非常清楚:
# 【实现类全名=声明类型的包 + "." + prefix + 声明类型名 + suffix】
###########################################################
dao.impl.class.prefix=impl.
dao.impl.class.suffix=Impl

service.impl.class.prefix=impl.
service.impl.class.suffix=Impl

实体类User

package jw.jwweb.mock.entity;

public class User {

    private int id;

    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

ResultSet转对象

package jw.jwweb.mock.utils;

import java.sql.ResultSet;
import java.sql.SQLException;

import jw.jwweb.mock.entity.User;
import cn.wensiqun.wheel.util.rs.AbstractResultSetConverter;

public class UserResultConverter extends AbstractResultSetConverter {

    @Override
    protected User convertToEntity(ResultSet rs) throws SQLException {
        User user = new User();
        user.setId(rs.getInt("id"));
        user.setName(rs.getString("name"));
        return user;
    }

}

这里要注意,public class UserResultConverter extends AbstractResultSetConverter这里的AbstractResultSetConverter应该加个泛型为:AbstractResultSetConverter。不知道为什么,这个代码高亮工具没法加

所有想要将Result转换成实体类的都可以通过这种方式实现,使用的时候也非常方便,只要调用ResultSetConverterContext.convertToEntities(User.class, rs)就能返回对应的List了。主要归功于Wheel在系统初始化的时候就已经找到所有Coverter并且实例化了。这里要注意的是每一个Converter在系统运行的时候只有一个实例,并且没有线程同步。

DAO层SQL Properties文件(MockDaoImplSQL.properties)

user = user_ AS u
user.id = u.id
user.name = u.name
all.user = SELECT ${user.id},${user.name} FROM ${segment.user}
这里的properties文件的命名是规则是 DAO class name + SQL.properties,并且要和dao的class放在同一个包下,也就是说这里我们还有一个Dao叫做MockDaoImpl。 我们可以看到sql的properties文件中可以用占位符的方式,也就是说将${...}这部分内容替换成properties中对应的内容。那么如何在DAO中使用这些SQL呢,接下来就进入DAO的编写。

MockDaoImpl

package jw.jwweb.mock.dao.impl;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

import cn.wensiqun.wheel.db.template.MySQLTemplate;
import cn.wensiqun.wheel.util.rs.ResultSetConverterContext;

import jw.jwweb.mock.dao.MockDao;
import jw.jwweb.mock.entity.User;

public class MockDaoImpl extends MySQLTemplate implements MockDao {

    @Override
    public void insertUser(User user) {

    }

    @Override
    public void deleteUser(User user) {

    }

    @Override
    public void updateUser(User user) {

    }

    @Override
    public List allUser() throws Exception{
        PreparedStatement prepareStatement = getStatement(getSql("all.user"));
        ResultSet rs = prepareStatement.executeQuery();
        return ResultSetConverterContext.convertToEntities(User.class, rs);
    }

    @Override
    public List fuzzyQueryUser(User user) {
        // TODO Auto-generated method stub
        return null;
    }

}
有用我用的是Mysql所以这里我继承了MySQLTemplate这个类,这里还有OracleTemplate类,是Oracle的实现,当然也可以自定义实现。在Wheel中不同数据库的DBTemplate的主要区别是在于分页和获取最新插入的数据的自动增长列的值,在这里不详细介绍如何实现一个Template,在后续教程中将继续讲述。 这里只简单的实现下allUser方法,我们可以看到,getSql这个方法的调用,就是获取MockDaoImplSQL.properties中all.user所对应的sql。而getStatement就是获取sql对应的PreparedStatement,在wheel中所有的Statment都是基于PreparedStatement的封装。getStatement方法后面还有一个变元的参数,是传入sql中对应位置的值的,也就是"?"下对应的值,位置的顺序要一一对应上。

Service层 MockServiceImpl

package jw.jwweb.mock.service.impl;

import java.util.List;

import jw.jwweb.mock.dao.MockDao;
import jw.jwweb.mock.dao.impl.MockDaoImpl;
import jw.jwweb.mock.entity.User;
import jw.jwweb.mock.service.MockService;
import cn.wensiqun.wheel.annotation.DAO;
import cn.wensiqun.wheel.annotation.Singleton;
import cn.wensiqun.wheel.annotation.Transaction;

@Singleton
public class MockServiceImpl implements MockService {

    @DAO
    private MockDao mockDao;

    @Transaction
    @Override
    public List getUsers() throws Exception {
        return mockDao.allUser();
    }

    @Transaction
    @Override
    public void addUser() throws Exception {

    }
}
这里是Service层的实现,我们可以看到有三个注解, @Singleton @DAO @Transaction。下面一一解释这几个注解的作用.

@Singleton

表示当前类的对象在内存中之存在一个。

@DAO

注入一个Dao层的对象,这里有两种方式注入。
  • 将实现类作为参数传入@DAO注解
  • 通过配置文件配置实现类的查找规则。

将实现类作为参数传入@DAO注解

这种方式非常简单,比如我们有个实现类是MockDaoImp,那么直接在注入的时候写成@DAO(MockDaoImpl.class)即可

通过配置文件配置实现类的查找规则。

当未通过将实现类作为参数传入@DAO注解的方式的时候我们将进入这种方式的实现类查找鬼子, 这里也有两种查规则的方式,第一种是配置前后缀的放方式,第二种是配置静态方法的方式。首先看第一种
配置前后缀方式
在之前我们讲到过如何配置global.properties,那么在这个配置文件中存在两个属性dao.impl.class.prefix和dao.impl.class.suffix。这两个就是我们需要配置的选项里,前面的是表示配置的前缀,后面的表示后缀,那么我们是如何通过这两个属性查找实现类的呢。它实际上就是将我们的声明的Dao的类型分成两部分,第一个是包名,第二个是类型名,如果这里的jw.jwweb.mock.dao.MockDao我们将其分成了包名jw.jwweb.mock.dao.( 注意这个包名最后是有一个点号的)和类型名MockDao,然后再在将做如下运算得到实现类的全名:"包名 + prefix + 类型名 + suffix".比如这里我们配置的prefix和suffix分别是"impl."和Impl,那么这里查找的实现类就是jw.jwweb.mock.dao.impl.MockDaoImpl。
配置静态方法
这里需要配置global.properties中的另一个属性"dao.impl.class.convert.method", 这个属性将配置一个静态方法的名称的全面,wheel在查找实现类的时候将会将我们声明的类型的全名(这里是jw.jwweb.mock.dao.MockDao)作为一个String传入到所配置的静态方法中,然后将它的返回值作为实现类的类型。这里所配置的静态方法必须满足如下两个条件:
  • 静态的方法
  • 返回类型是String
  • 有且只有一个参数,并且其类型是String类型,这里的参数表示类型的全名,也就是包括了包名的.

这里如果配置了dao.impl.class.convert.method则dao.impl.class.prefix和dao.impl.class.suffix将无效,也就是说dao.impl.class.convert.method优先级更高

假设我们有个静态方法:
package jw.jwweb.mock

public class Utils{
    public static String convert(String interfaceFullName){
        return interfaceFullName+"Impl"
    }
}
我们配置dao.impl.class.convert.method=jw.jwweb.mock.Utils.convert, 那么wheel就会找到实现类的类名是jw.jwweb.mock.dao.MockDaoImpl。

@Transaction

这个是事务的配置,加载方法上面的,如我我们希望这个方法在调用的时候采用事务就可以加入这个注解,这个注解还可以传入一个参数表示事务级别。比如@Transaction(level=Transaction.TRANSACTION_READ_UNCOMMITTED)。需要注意的是这个注解只能用在Service层的class中

MockServletAction

package jw.jwweb.mock.servlet;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jw.jwweb.mock.entity.User;
import jw.jwweb.mock.service.impl.MockServiceImpl;
import cn.wensiqun.wheel.annotation.Service;
import cn.wensiqun.wheel.mvc.annotation.Action;
import cn.wensiqun.wheel.mvc.annotation.DefaultResultType;
import cn.wensiqun.wheel.mvc.annotation.RequestMethod;
import cn.wensiqun.wheel.mvc.annotation.UrlMapping;
import cn.wensiqun.wheel.mvc.aware.HttpServletRequestAware;
import cn.wensiqun.wheel.mvc.aware.HttpServletResponseAware;

@Action("/MockServlet")
public class MockServletAction implements HttpServletRequestAware, HttpServletResponseAware{

	@Service(MockServiceImpl.class)
	private MockServiceImpl mockService;

	private HttpServletRequest request;

	private HttpServletResponse response;

	@UrlMapping(
			value=       {"/execute.action", "/process.action"},
			method =     {RequestMethod.POST, RequestMethod.GET},
			result=      {"JSP_RES",
					      "Redirect_RES",
					      "plaintext_RES",
					      "html_RES",
					      "json_RES",
					      "velocity_RES"},
			resultType = {DefaultResultType.JSP,
					      DefaultResultType.REDIRECT,
					      DefaultResultType.PLAINTEXT, 
					      DefaultResultType.HTML,
					      DefaultResultType.JSON,
					      DefaultResultType.VELOCITY},
			path=        {"/servlet/test.jsp",
					      "/servlet/test.jsp",
					      "",
					      "",
					      "",
					      "/WEB-INF/test.vm"}
	)
	public String execute() throws IOException {
		String type = request.getParameter("type");

		String userStr = "";
    	try {
			List users = mockService.getUsers();
			if(users != null){
				for(User u : users){
					userStr += u.getName() + " ";
				}
			}
		} catch (Exception e) {
			userStr = "error read user!" + e.getMessage();
		}

		if("FORWARD".equals(type)){
			request.setAttribute("message", userStr + "i'm from forward");
			return "JSP_RES";
		}else if("REDIRECT".equals(type)){
			return "Redirect_RES";
		}else if("PLAINTEXT".equals(type)){
			PrintWriter out = response.getWriter();
			out.println("{\"success\" : true, \"message\" : " + userStr + "\"i'm from PLAINTEXT\"}");
			out.close();
			return "plaintext_RES";
		}else if("VELOCITY".equals(type)){
			request.setAttribute("resultMessage", userStr + " I'm from Velocity.");
			return "velocity_RES";
		}else if("JSON".equals(type)){
			Map<String, String> result = new HashMap<String, String>();
			result.put("message", userStr + " I'm from JSON");
			request.setAttribute(DefaultResultType.JSON, result);
			return "json_RES";
		}else{
			PrintWriter out = response.getWriter();
			out.println("");
			out.println("HTML Test");
			out.println("" + userStr + "I'm from HTML");
			out.println("");
			out.close();
			return "html_RES";
		}
	}

	@Override
	public void setHttpServletResponse(HttpServletResponse response) {
		this.response = response;
	}

	@Override
	public void setHttpServletRequest(HttpServletRequest request) {
		this.request = request;
	}
}
这段就是我们熟知的Action层,这一层所包含的内容非常多我们一一解释,首先是注入。

注入

Action层中可以注入Service也可以注入Dao,如果注入的是Service则用 @Service ,如果是Dao则用@DAO。@Service和@DAO这两个注解用法一样,可以参照前面Service层 MockServiceImpl里面讲的内容。

HttpServletRequestAware和HttpServletResponseAware接口

这两个接口主要是为了获取HttpServletRequest对象和HttpServletResponse对象,当请进进入到这个类之前,如果当前类实现了HttpServletRequestAware接口就会调用setHttpServletRequest(HttpServletRequest request)方法,将HttpServletRequest传入当前Action的对象,如果实现了HttpServletResponseAware接口就会调用setHttpServletResponse(HttpServletResponse response)方法,将HttpServletResponse。需要注意的一点就是如果当前Action的类使用了@Singleton注解,那么这个Action在内存中只有一个,那么得对当前Action类做同步。

请求分发

这里是MVC中的核心部分,这里关联到两个注解,@Action和@UrlMapping

@Action

这个注解有两个作用,第一个是用是告诉Wheel,这个类表示的是一个Action类,第二个作用就是对请求进行第一层的筛选,比如在上面的例子中我们为当前类添加了注解@Action("/MockServlet"), 那么首先所有以/MockServlet开头的url请求都会进入到当前的类来处理。

@UrlMapping

这个注解就是做第二层筛选,同时找到对应的业务处理方法和视图展示方法。这个注解包括了如下参数:
  • value : 字符数组类型
  • method : RequestMethod枚举数组类型
  • result : 字符数组类型
  • resultType : 字符数组类型
  • path : 字符数组类型
我们可以将上面五种参数分成两类,第一类是value和method,他们的作用是将请求导向到对应的方法处理;剩余的属性则是第二类,他们的作用是对不同的处理结果采用对应视图展示方式。
第一类:

value

这个值将配合@Action中配置值一起使用,告诉Wheel当前这个方法是处理那个请求的,比如这里配置的是value = {"/execute.action", "/process.action"},那么当前的方法就是处理/MockServlet/execute.action请求和/MockServlet/process.action请求的。注意这个后缀名一定要加上,并且得符合我们之前在web.xml里面配置的cn.wensiqun.wheel.mvc.dispatcher.ActionServlet对应的url-pattern。

method

这里是告诉Wheel,请求必须采用什么方式提交的,默认是处理POST提交的。这里我们配置的是method = {RequestMethod.POST, RequestMethod.GET}也就是说在确认请求的url符合我们所制定的时候,当前这个请求还得采用POST或者GET提交。这里处理POST和GET之外还有HEAD,PUT,DELETE,OPTIONS,TRACE.
第二类: 我们看到第二类参数都是数组类型的,需要特别注意的是他们的数组长度必须相同,并且数组每一个元素要一一对应,也就是说result[0]对应path[0]和resultType[0].

result

配置当前方法的所有可能的返回结果类型,默认是{"success"}, 上面的代码中总共有五种返回值{"JSP_RES","Redirect_RES","plaintext_RES","html_RES","json_RES","velocity_RES"}。

resultType

每一种返回类型对应的视图处理方式,Wheel已经提供6中方式的视图分别是JSP,Redirect,plaintext,html,json,velocity。当然我们也可以自定义不同的视图实现,这个将在以后介绍。

path

这是对应的每种视图处理所需要文件的路径,比如jsp地文件路径是"/servlet/test.jsp",velocity文件对应的位置是"/WEB-INF/test.vm"。这里有点需要注意,有的视图是不需要对应文件的,那么你填写任何值都可以,在这个例子中我们填写的是""。 通过上面的配置我们可以简单的用语言描述前三种处理结果:
  • 当返回结果为"JSP_RES"的时候我们采用JSP视图进行显示,jsp文件对应的位置是"/servlet/test.jsp"
  • 当返回结果为"Redirect_RES"的时候我们采用直接跳转的方式显示,文件对应的位置是"/servlet/test.jsp"
  • 当返回结果为"plaintext_RES"的时候我们采用直接输出文本流的方式显示

视图处理

Wheel内置了有六种视图,在上面介绍过,并且我们也可以自定义不同的视图,如何实现自定义的视图在以后教程中将会介绍,这里只提下JSON视图,使用这个视图我们必须想request里面加入一个名为"JSON"的属性,而这个属性对应的值就是我们希望转换成JSON的java对象,正如上面代码中request.setAttribute(DefaultResultType.JSON, result)。DefaultResultType.JSON是一个静态常量,它的值就是"JSON"。

test.jsp和test.vm

test.jsp是JSP视图和REDIRECT视图所采用的,而test.vm是velocity视图所采用的。他们分别放置在更目录下的servlet文件夹下面和WEB-INF文件夹下面。那么他们具体的内容很简单如下:

test.jsp

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Success</title>
</head>
<body>
<%
Object obj = request.getAttribute("message");
String message = "";
if(obj==null){
    message = "I'm from REDIRECT";
}else{
    message = obj.toString();
}
%>
<%=message %>
</body>
</html>

test.vm

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Success</title>
</head>
<body>
${resultMessage}
</body>
</html>

执行结果

我们依次执行下面url:
URL 结果
http://host/WheelSampleApp/MockServlet/process.action?type=FORWARD 浏览器显示asmsupport wheel i'm from forward
http://host/WheelSampleApp/MockServlet/execute.action?type=FORWARD 浏览器显示asmsupport wheel i'm from forward
http://host/WheelSampleApp/MockServlet/process.action?type=REDIRECT 浏览器显示I'm from REDIRECT,同时浏览器url变成http://host/WheelSampleApp/servlet/test.jsp
http://host/WheelSampleApp/MockServlet/execute.action?type=PLAINTEXT 浏览器显示{"success" : true, "message" : asmsupport wheel "i'm from PLAINTEXT"}
http://host/WheelSampleApp/MockServlet/process.action?type=VELOCITY 浏览器显示asmsupport wheel I'm from Velocity.
http://host/WheelSampleApp/MockServlet/process.action?type=JSON 浏览器显示{"message":"asmsupport wheel I'm from JSON"}
http://host/WheelSampleApp/MockServlet/process.action?type=HTML 浏览器显示asmsupport wheel I'm from HTML

你可能感兴趣的:(Wheel)