这个教程,主要就是介绍如何使用MVC框架Wheel.简单的描述下我们想要做的事情,创建一个user表,然后通过不同的视图方式显示出user表的内容。我们采用Mysql数据库.
数据库
1DROP TABLE IF EXISTS `user_`; 2CREATE TABLE `user_` ( 3 `id` int(11) NOT NULL AUTO_INCREMENT, 4 `name` varchar(45) NOT NULL, 5 `CREATE_DATE_TIME` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 6 `MODIFY_DATE_TIME` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', 7 `CREATE_USER_ID` int(10) NOT NULL, 8 `MODIFY_USER_ID` int(10) NOT NULL, 9 `DELETED` int(1) NOT NULL DEFAULT '0', 10 PRIMARY KEY (`id`), 11 UNIQUE KEY `name_UNIQUE` (`name`) 12) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 13 14INSERT 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就可以了。
1 <servlet> 2 <servlet-name>jwmvc</servlet-name> 3 <servlet-class>cn.wensiqun.wheel.mvc.dispatcher.ActionServlet</servlet-class> 4 </servlet> 5 6 <servlet-mapping> 7 <servlet-name>jwmvc</servlet-name> 8 <url-pattern>*.*</url-pattern> 9 </servlet-mapping>
global.properties
这个文件是Wheel的一个配置文件,具体有哪些属性可以查看源码中根路径下的"global.default.properties".这里我们只做其中几项配置
1#数据库配置信息 2jdbc.driverClassName=com.mysql.jdbc.Driver 3jdbc.url=jdbc:mysql://localhost:3306/test 4jdbc.username=root 5jdbc.password=mysql 6 7########################################################## 8# 是否是在运行时生成类,默认是true,当选择false的时候,需要使用 9# wheel的maven插件wheel-maven-plugin,这个插件的作用就是将生成 10# 的类添加到项目war包中,并且替换原有的class,使用这种方式的好处 11# 就是减少系统的开销 12########################################################## 13generator.class.runtime=true 14 15########################################################## 16# 将我们生成的class存入到指定路径,如果没有设置这个属性的值,将 17# 不会输出生产的类。输出我们生成的class可以便于用反编译工具查看 18# 对应的源码,这样可以更直观的清楚wheel的原理。当然也可能反编译 19# 出来的源码内容不正确,具体原因可以参考 20# http://www.wensiqun.com/2013/06/09/asmsupport_tutorial_3.html 21########################################################## 22generator.class.output=D:/TEMP/mock_generated 23 24########################################################## 25# Action类搜索路径,wheel会在这个路径里面查找被Action注解过的Class 26# 生成代理类,和SpringMVC中的 27# 28# 类似。 29# 这个路径也是我们存放ResultSetConverter的路径。 30########################################################## 31scan.base.path=cn.wensiqun.,jw. 32 33########################################################## 34# Dao层接口的实现类的匹配前缀,比如接口com.wensiqun.wheel.TestDao 35# 如果我们设置 36# dao.impl.class.suffix=impl. 37# dao.impl.class.suffix=JDBC 38# 那么我们将去寻找的实现类是com.wensiqun.wheel.impl.TestDaoJDBC, 39# 通过如下公式可以非常清楚: 40# 【实现类全名=声明类型的包 + "." + prefix + 声明类型名 + suffix】 41########################################################### 42dao.impl.class.prefix=impl. 43dao.impl.class.suffix=Impl 44 45service.impl.class.prefix=impl. 46service.impl.class.suffix=Impl
实体类User
1package jw.jwweb.mock.entity; 2 3public class User { 4 5 private int id; 6 7 private String name; 8 9 public int getId() { 10 return id; 11 } 12 13 public void setId(int id) { 14 this.id = id; 15 } 16 17 public String getName() { 18 return name; 19 } 20 21 public void setName(String name) { 22 this.name = name; 23 } 24 25}
ResultSet转对象
1package jw.jwweb.mock.utils; 2 3import java.sql.ResultSet; 4import java.sql.SQLException; 5 6import jw.jwweb.mock.entity.User; 7import cn.wensiqun.wheel.util.rs.AbstractResultSetConverter; 8 9public class UserResultConverter extends AbstractResultSetConverter { 10 11 @Override 12 protected User convertToEntity(ResultSet rs) throws SQLException { 13 User user = new User(); 14 user.setId(rs.getInt("id")); 15 user.setName(rs.getString("name")); 16 return user; 17 } 18 19}
这里要注意,public class UserResultConverter extends AbstractResultSetConverter这里的AbstractResultSetConverter应该加个泛型为:AbstractResultSetConverter。不知道为什么,这个代码高亮工具没法加
所有想要将Result转换成实体类的都可以通过这种方式实现,使用的时候也非常方便,只要调用ResultSetConverterContext.convertToEntities(User.class, rs)就能返回对应的List了。主要归功于Wheel在系统初始化的时候就已经找到所有Coverter并且实例化了。这里要注意的是每一个Converter在系统运行的时候只有一个实例,并且没有线程同步。
DAO层SQL Properties文件(MockDaoImplSQL.properties)
1user = user_ AS u 2user.id = u.id 3user.name = u.name 4all.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
1package jw.jwweb.mock.dao.impl; 2 3import java.sql.PreparedStatement; 4import java.sql.ResultSet; 5import java.util.ArrayList; 6import java.util.List; 7 8import cn.wensiqun.wheel.db.template.MySQLTemplate; 9import cn.wensiqun.wheel.util.rs.ResultSetConverterContext; 10 11import jw.jwweb.mock.dao.MockDao; 12import jw.jwweb.mock.entity.User; 13 14public class MockDaoImpl extends MySQLTemplate implements MockDao { 15 16 @Override 17 public void insertUser(User user) { 18 19 } 20 21 @Override 22 public void deleteUser(User user) { 23 24 } 25 26 @Override 27 public void updateUser(User user) { 28 29 } 30 31 @Override 32 public List allUser() throws Exception{ 33 PreparedStatement prepareStatement = getStatement(getSql("all.user")); 34 ResultSet rs = prepareStatement.executeQuery(); 35 return ResultSetConverterContext.convertToEntities(User.class, rs); 36 } 37 38 @Override 39 public List fuzzyQueryUser(User user) { 40 // TODO Auto-generated method stub 41 return null; 42 } 43 44}
有用我用的是Mysql所以这里我继承了MySQLTemplate这个类,这里还有OracleTemplate类,是Oracle的实现,当然也可以自定义实现。在Wheel中不同数据库的DBTemplate的主要区别是在于分页和获取最新插入的数据的自动增长列的值,在这里不详细介绍如何实现一个Template,在后续教程中将继续讲述。 这里只简单的实现下allUser方法,我们可以看到,getSql这个方法的调用,就是获取MockDaoImplSQL.properties中all.user所对应的sql。而getStatement就是获取sql对应的PreparedStatement,在wheel中所有的Statment都是基于PreparedStatement的封装。getStatement方法后面还有一个变元的参数,是传入sql中对应位置的值的,也就是"?"下对应的值,位置的顺序要一一对应上。
Service层 MockServiceImpl
1package jw.jwweb.mock.service.impl; 2 3import java.util.List; 4 5import jw.jwweb.mock.dao.MockDao; 6import jw.jwweb.mock.dao.impl.MockDaoImpl; 7import jw.jwweb.mock.entity.User; 8import jw.jwweb.mock.service.MockService; 9import cn.wensiqun.wheel.annotation.DAO; 10import cn.wensiqun.wheel.annotation.Singleton; 11import cn.wensiqun.wheel.annotation.Transaction; 12 13@Singleton 14public class MockServiceImpl implements MockService { 15 16 @DAO 17 private MockDao mockDao; 18 19 @Transaction 20 @Override 21 public List getUsers() throws Exception { 22 return mockDao.allUser(); 23 } 24 25 @Transaction 26 @Override 27 public void addUser() throws Exception { 28 29 } 30}
这里是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优先级更高
假设我们有个静态方法:
1package jw.jwweb.mock 2 3public class Utils{ 4 public static String convert(String interfaceFullName){ 5 return interfaceFullName+"Impl" 6 } 7}
我们配置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
1package jw.jwweb.mock.servlet; 2 3import java.io.IOException; 4import java.io.PrintWriter; 5import java.util.HashMap; 6import java.util.List; 7import java.util.Map; 8 9import javax.servlet.http.HttpServletRequest; 10import javax.servlet.http.HttpServletResponse; 11 12import jw.jwweb.mock.entity.User; 13import jw.jwweb.mock.service.impl.MockServiceImpl; 14import cn.wensiqun.wheel.annotation.Service; 15import cn.wensiqun.wheel.mvc.annotation.Action; 16import cn.wensiqun.wheel.mvc.annotation.DefaultResultType; 17import cn.wensiqun.wheel.mvc.annotation.RequestMethod; 18import cn.wensiqun.wheel.mvc.annotation.UrlMapping; 19import cn.wensiqun.wheel.mvc.aware.HttpServletRequestAware; 20import cn.wensiqun.wheel.mvc.aware.HttpServletResponseAware; 21 22@Action("/MockServlet") 23public class MockServletAction implements HttpServletRequestAware, HttpServletResponseAware{ 24 25 @Service(MockServiceImpl.class) 26 private MockServiceImpl mockService; 27 28 private HttpServletRequest request; 29 30 private HttpServletResponse response; 31 32 @UrlMapping( 33 value= {"/execute.action", "/process.action"}, 34 method = {RequestMethod.POST, RequestMethod.GET}, 35 result= {"JSP_RES", 36 "Redirect_RES", 37 "plaintext_RES", 38 "html_RES", 39 "json_RES", 40 "velocity_RES"}, 41 resultType = {DefaultResultType.JSP, 42 DefaultResultType.REDIRECT, 43 DefaultResultType.PLAINTEXT, 44 DefaultResultType.HTML, 45 DefaultResultType.JSON, 46 DefaultResultType.VELOCITY}, 47 path= {"/servlet/test.jsp", 48 "/servlet/test.jsp", 49 "", 50 "", 51 "", 52 "/WEB-INF/test.vm"} 53 ) 54 public String execute() throws IOException { 55 String type = request.getParameter("type"); 56 57 String userStr = ""; 58 try { 59 List users = mockService.getUsers(); 60 if(users != null){ 61 for(User u : users){ 62 userStr += u.getName() + " "; 63 } 64 } 65 } catch (Exception e) { 66 userStr = "error read user!" + e.getMessage(); 67 } 68 69 if("FORWARD".equals(type)){ 70 request.setAttribute("message", userStr + "i'm from forward"); 71 return "JSP_RES"; 72 }else if("REDIRECT".equals(type)){ 73 return "Redirect_RES"; 74 }else if("PLAINTEXT".equals(type)){ 75 PrintWriter out = response.getWriter(); 76 out.println("{\"success\" : true, \"message\" : " + userStr + "\"i'm from PLAINTEXT\"}"); 77 out.close(); 78 return "plaintext_RES"; 79 }else if("VELOCITY".equals(type)){ 80 request.setAttribute("resultMessage", userStr + " I'm from Velocity."); 81 return "velocity_RES"; 82 }else if("JSON".equals(type)){ 83 Map<String, String> result = new HashMap<String, String>(); 84 result.put("message", userStr + " I'm from JSON"); 85 request.setAttribute(DefaultResultType.JSON, result); 86 return "json_RES"; 87 }else{ 88 PrintWriter out = response.getWriter(); 89 out.println(""); 90 out.println("HTML Test"); 91 out.println("" + userStr + "I'm from HTML"); 92 out.println(""); 93 out.close(); 94 return "html_RES"; 95 } 96 } 97 98 @Override 99 public void setHttpServletResponse(HttpServletResponse response) { 100 this.response = response; 101 } 102 103 @Override 104 public void setHttpServletRequest(HttpServletRequest request) { 105 this.request = request; 106 } 107}
这段就是我们熟知的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
1<%@ page language="java" contentType="text/html; charset=ISO-8859-1" 2 pageEncoding="ISO-8859-1"%> 3<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 4<html> 5<head> 6<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> 7<title>Success</title> 8</head> 9<body> 10<% 11Object obj = request.getAttribute("message"); 12String message = ""; 13if(obj==null){ 14 message = "I'm from REDIRECT"; 15}else{ 16 message = obj.toString(); 17} 18%> 19<%=message %> 20</body> 21</html>
test.vm
1<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 2<html> 3<head> 4<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 5<title>Success</title> 6</head> 7<body> 8${resultMessage} 9</body> 10</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