这个教程,主要就是介绍如何使用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