自定义MVC完整版
前言
来自全世界各地的朋友们,你们好!小编前面的博客中有简单的介绍MVC,建议初学者先去看之前写的博客,再连贯起来,关于自定义mvc的博客链接如下表格(从上往下看,就是从最初的版本演变成最终的自定义MVC完整的版本的过程):
1.需要实现的效果图(目标)
2.做增删改查的前两种方案的弊端
2.1第一种方案:jsp版本(jsp页面+servlet)
弊端:同样是处理书籍的逻辑业务(crud),创建的类过多,假如我有100张表那么我要创建400个处理"增删改查"的"处理业务逻辑"的servlet类
2.2第二种方案:if分支处理
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String methodName = req.getParameter("methodName");
if("list".equals(methodName)) {
list(req, resp);
}else if("add".equals(methodName)) {
add(req, resp);
}else if("toEdit".equals(methodName)) {
toEdit(req, resp);
}else if("edit".equals(methodName)) {
edit(req, resp);
}else if("del".equals(methodName)) {
del(req, resp);
}
}
思考:能不能把所有相关的一组业务逻辑(比如都是关于书籍的操作)放在一个类中进行处理?
发现:这种处理方式少维护"n-1"个类
弊端:
-
if条件分支过多,代码过于"臃肿",也就是说太胖了,看起来不好看(分析:if代码块中就是调用了当前类的对应方法。 处理:“反射动态调用方法”)
-
处理前端jsp传递到后端值得封装"代码量过大"(分析:说白了就是给指定的"类属性"赋值 处理:“通过反射读写属性”)
-
处理完"业务逻辑"应该"跳转指定页面"(分析:不同的逻辑处理完跳转的页面不同 ,代码的位置过于混乱,我们希望统一管理 处理:“利用"xml建模"将"结果码"页面统一配置”)
3.自定义mvc框架工作原理图
原理图中的讲解思路(如下):
4.整个项目所需的所有包及类
5.所需的数据库表及数据
6.项目源代码
Job.java(实体类):
package com.xiaoqing.entity;
import java.sql.Timestamp;
public class Job {
private String id;
private String job;
private String company;
private String address;
private String salary;
private String url;
private String quary;
private Timestamp timedate;
public Timestamp getTimedate() {
return timedate;
}
public void setTimedate(Timestamp timedate) {
this.timedate = timedate;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
public String getCompany() {
return company;
}
public void setCompany(String company) {
this.company = company;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getSalary() {
return salary;
}
public void setSalary(String salary) {
this.salary = salary;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getQuary() {
return quary;
}
public void setQuary(String quary) {
this.quary = quary;
}
public Job() {}
public Job(String id, String job, String company, String address, String salary, String url, String quary) {
this.id = id;
this.job = job;
this.company = company;
this.address = address;
this.salary = salary;
this.url = url;
this.quary = quary;
}
public Job(String job, String company, String address, String salary, String url, String quary) {
this.job = job;
this.company = company;
this.address = address;
this.salary = salary;
this.url = url;
this.quary = quary;
}
@Override
public String toString() {
return "Job [id=" + id + ", job=" + job + ", company=" + company + ", address=" + address + ", salary=" + salary
+ ", url=" + url + ", quary=" + quary + "]";
}
public Job(String id, String job, String company, String address, String salary, String url, String quary,
Timestamp timedate) {
super();
this.id = id;
this.job = job;
this.company = company;
this.address = address;
this.salary = salary;
this.url = url;
this.quary = quary;
this.timedate = timedate;
}
}
DispatcherServlet .java(中央控制器):
/**
* 中央控制器
* 作用:接受请求,通过请求寻找处理请求的对应的子控制器
* @author 晴sister
*
* https://i.csdn.net/#/uc/profile
*/
//配置中央控制器类(注):不要把*.action写成/*,不然连jsp文件也一起配置,此处只需要配置后缀名为.action的文件
@WebServlet(name="dispatcherServlet",urlPatterns="*.action")
public class DispatcherServlet extends HttpServlet{
private static final long serialVersionUID = 3087790807485948710L;
//建模
private ConfigModel configModel;
//初始化
@Override
public void init() throws ServletException {
try {
configModel=ConfigModelFactory.build();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//拿到发出的请求:http://localhost:8080/T243_mvc2/job.action?methodName=list
String uri = req.getRequestURI();
//System.out.println(uri);
//截取job
String path = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf("."));//book
//通过建模拿到<action>标签
ActionModel actionModel = configModel.pop(path);
//如果没有配置<action>标签
if(actionModel==null) {
//则抛出异常
throw new RuntimeException(path+" action 标签没有配置");
}
try {
//实例化处理网络请求URL的类
//action就相当于JobAction
//反射实例化 newInstance
Action action =(Action)Class.forName(actionModel.getType()).newInstance();
//动态封装参数
if(action instanceof ModelDriver) {
//如果实现了模型驱动接口,那么就强转为ModelDriver 此时action代表JobAction
ModelDriver md = (ModelDriver)action;
//将前端jsp参数传递到后端的所有值封装到业务模型类中
//这个方法会遍历map<key, value>中的key,如果md.getModel()中有这个属性,就把这个key对应的value值赋给md.getModel()的属性。
BeanUtils.populate(md.getModel(), req.getParameterMap());
}
//动态调用方法:解决if分支代码块的问题
String code = action.excute(req, resp);//得到结果码
//通过结果码去找对应跳转的哪个页面
ForwardModel forwardModel = actionModel.pop(code);
//得到path :reg.jsp(例如)
String jspPath=forwardModel.getPath();
//如果是重定向
if(forwardModel.getRedirect()) {
resp.sendRedirect(req.getServletContext()+jspPath);
}else {
//不是重定向,转发
req.getRequestDispatcher(jspPath).forward(req, resp);
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
}
Action.java(子控制器接口):
/**
* 子控制器(相当于火车轨)
* 作用:用来处理浏览器发送过来的请求
* @author 晴sister
*
* https://i.csdn.net/#/uc/profile
*/
public interface Action {
//执行业务逻辑代码
String excute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException ;
}
ActionSupport .java(子控制器):
/**
* 处理if分支问题,动态调用当前类的其他方法add/del/edit
* @author 晴sister
*
* https://i.csdn.net/#/uc/profile
*/
public class ActionSupport implements Action{
@Override
public String excute(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
String methodName = req.getParameter("methodName");
//this指的是JobAction
//通过反射得到方法名
Method m = this.getClass().getDeclaredMethod(methodName, HttpServletRequest.class,HttpServletResponse.class);
//打开通道
m.setAccessible(true);
//相当于动态调用del(req,resp)
//method.invoke()方法:用来执行某个的对象的目标方法
return (String) m.invoke(this, req,resp);
}
}
ModelDriver.java(模型驱动接口):
/**
* 模型驱动接口
* @author 晴sister
*
* https://i.csdn.net/#/uc/profile
*/
public interface ModelDriver<T> {
//T 在此代表private Job job = new Job();
//得到具体的类
T getModel();
}
PageTag .java(封装分页标签助手类):
public class PageTag extends BodyTagSupport{
private static final long serialVersionUID = -258029245678348536L;
private PageBean pageBean;
public PageBean getPageBean() {
return pageBean;
}
public void setPageBean(PageBean pageBean) {
this.pageBean = pageBean;
}
@Override
public int doStartTag() throws JspException {
// TODO Auto-generated method stub
JspWriter out = pageContext.getOut();
try {
out.print(toHTML());
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
return super.doStartTag();
}
private String toHTML() {
// TODO Auto-generated method stub
StringBuilder sb=new StringBuilder();
//上一次查询的form表单的HTML拼接
//System.out.println(pageBean.getUrl());
sb.append(" <form id='pageBeanForm' action='"+pageBean.getUrl()+"' method='post'>");
//查第几页的数据
sb.append(" <input type='hidden' name='page'>");
Map<String, String[]> parameterMap = pageBean.getParameterMap();
if(parameterMap.size()>0) {
Set<Entry<String,String[]>> entrySet = parameterMap.entrySet();
for (Entry<String, String[]> entry : entrySet) {
if(!"page".equals(entry.getKey())) {
for (String val : entry.getValue()) {
sb.append("<input type='hidden' value='"+val+"' name='"+entry.getKey()+"'>");
}
}
}
}
sb.append("</form> ");
//默认展示前面4页,当前页 ,后面5也
int page=pageBean.getPage();
int max=pageBean.getMaxPage();
int before=page>4?4:page-1;
int after=10-1-before;//5
after=max-page>after?after:max-page;
//用来控制上一页的点击按钮特效的
boolean startFlag=page==1;
//用来控制下一页的点击按钮特效的
boolean endFlag=page==max;
// 拼接分页条
sb.append("<ul class='pagination'>");
sb.append("<li class='page-item "+(startFlag ? "disabled" : "")+"'><a class='page-link' href='javascript:gotoPage(1)'>首页</a></li>");
sb.append("<li class='page-item "+(startFlag ? "disabled" : "")+"'><a class='page-link' href='javascript:gotoPage("+pageBean.getPrevPage()+")'><</a></li>");
// 代表了当前页的前4页
for (int i = before; i > 0 ; i--) {
sb.append("<li class='page-item'><a class='page-link' href='javascript:gotoPage("+(page-i)+")'>"+(page-i)+"</a></li>");
}
sb.append("<li class='page-item active'><a class='page-link' href='javascript:gotoPage("+pageBean.getPage()+")'>"+pageBean.getPage()+"</a></li>");
// 代表了当前页的后5页
for (int i = 1; i <= after; i++) {
sb.append("<li class='page-item'><a class='page-link' href='javascript:gotoPage("+(page+i)+")'>"+(page+i)+"</a></li>");
}
sb.append("<li class='page-item "+(endFlag ? "disabled" : "")+"'><a class='page-link' href='javascript:gotoPage("+pageBean.getNextPage()+")'>></a></li>");
sb.append("<li class='page-item "+(endFlag ? "disabled" : "")+"'><a class='page-link' href='javascript:gotoPage("+pageBean.getMaxPage()+")'>尾页</a></li>");
sb.append("<li class='page-item go-input'><b>到第</b><input class='page-link' type='text' id='skipPage' name='' /><b>页</b></li>");
sb.append("<li class='page-item go'><a class='page-link' href='javascript:skipPage()'>确定</a></li>");
sb.append("<li class='page-item'><b>共"+pageBean.getTotal()+"条</b></li>");
sb.append("</ul>");
// 拼接分页的js代码
sb.append("<script type='text/javascript'>");
sb.append("function gotoPage(page) {");
sb.append("document.getElementById('pageBeanForm').page.value = page;");
sb.append("document.getElementById('pageBeanForm').submit();");
sb.append("}");
sb.append("function skipPage() {");
sb.append("var page = document.getElementById('skipPage').value;");
sb.append("if (!page || isNaN(page) || parseInt(page) < 1 || parseInt(page) > '+max+') {");
sb.append("alert('请输入1~N的数字');");
sb.append("return;");
sb.append("}");
sb.append("gotoPage(page);");
sb.append("}");
sb.append("</script>");
return sb.toString();
}
}
BaseDao.java(通用增删改查方法类):
public class BaseDao<T> {
public List<T> executeQuery(String sql,Class clz,PageBean pageBean) throws SQLException, InstantiationException, IllegalAccessException{
Connection con = DBAccess.getConnection();
PreparedStatement pst = null;
ResultSet rs = null;
//判断是否分页
if(pageBean!=null&&pageBean.isPagination()) {
//需要分页的
//算符合条件的总记录数
String countSql=getCountSql(sql);
//重新执行countSql
pst=con.prepareStatement(countSql);
rs=pst.executeQuery();
if(rs.next()) {
//设置总记录数
pageBean.setTotal(rs.getLong(1)+ "");
}
//查询出符合条件的结果集
String pageSql=getPageSql(sql,pageBean);
pst=con.prepareStatement(pageSql);
rs=pst.executeQuery();
}else {
//不需要分页的
pst=con.prepareStatement(sql);
rs=pst.executeQuery();
}
List<T> list=new ArrayList<T>();
T t;//为了节省内存,所以定义在while外面
while(rs.next()) {
/**
* 1.实例化一个book对象(该对象是空的,不是null)
* 2.取book的所有属性,然后给其赋值
* 2.1 获取所有属性对象
* 2.2 给属性对象赋值
* 3.赋完值的book对象装进list容器中
*/
//反射实例化
t =(T)clz.newInstance();
//反射获取所以属性对象
Field[] fields = clz.getDeclaredFields();
//遍历
for (Field field : fields) {
//打开通道
field.setAccessible(true);
//给属性对象赋值
field.set(t, rs.getObject(field.getName()));
//System.out.println(field.getName());
}
//把对象增加到集合
list.add(t);
}
DBAccess.close(con, pst, rs);
return list;
}
public String getCountSql(String sql) {
// TODO Auto-generated method stub
return "select count(1) from ("+sql+") t";
}
/**
* sql=select * from t_mvc_book where true and bname like '%圣墟%';
* countSql=select count(1) from (select * from t_mvc_book where true and bname like '%圣墟%')
* pageSql=select * from t_mvc_book where true and bname like '%圣墟%' limit 10,10;
*/
public String getPageSql(String sql,PageBean pageBean) {
return sql+" limit "+pageBean.getStartIndex()+","+pageBean.getRows();
}
/**
*
* @param sql
* @param t book
* @param attrs bid,bname,price
* @return
* @throws SQLException
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalAccessException
* @throws IllegalArgumentException
*/
public int executeUpdate(String sql,T t,String[] attrs) throws SQLException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
Connection con = DBAccess.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
System.out.println(sql);
/* ps.setObject(1, book.getBid());
ps.setObject(2, book.getBname());
ps.setObject(3, book.getPrice());*/
//以上三行代码的意思是,将参数对象t中的属性值,赋值给object对象
int loop=1;//下标
Field f=null;
//attrs={"bid","bname","price"}
for (String attr : attrs) {
f = t.getClass().getDeclaredField(attr);
f.setAccessible(true);
//给对象赋值
ps.setObject(loop++, f.get(t));
}
int n = ps.executeUpdate();
DBAccess.close(con, ps, null);
return n;
}
}
JobDao .java(处理sql语句):
public class JobDao extends BaseDao<Job>{
//查询所有及单个查询和模糊查询
public List<Job> list(Job job,PageBean pageBean) throws InstantiationException, IllegalAccessException, SQLException{
String sql="select * from t_solr_job where true";
String j = job.getJob();
if(StringUtils.isNotBlank(j)) {
sql+=" and job like '%"+j+"%'";
}
String id = job.getId();
if(StringUtils.isNotBlank(id)) {
sql+=" and id='"+id+"'";
}
return super.executeQuery(sql, Job.class, pageBean);
}
//增加
public int add(Job job) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, SQLException {
job.setId(UUID.randomUUID().toString());
String sql="insert into t_solr_job(id,job,company,address,salary,url,quary,timedate) values(?,?,?,?,?,?,?,?)";
return super.executeUpdate(sql, job, new String[] {"id","job","company","address","salary","url","quary","timedate"});
}
//修改
public int edit(Job job) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, SQLException {
String sql="update t_solr_job set job=?,company=?,address=?,salary=?,url=?,quary=? where id=?";
return super.executeUpdate(sql, job, new String[] {"job","company","address","salary","url","quary","id"});
}
//删除
public int del(Job job) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, SQLException {
String sql="delete from t_solr_job where id=?";
return super.executeUpdate(sql, job, new String[] {"id"});
}
}
JobAction .java(处理业务逻辑):
/**
* 子控制器:处理业务逻辑
* @author 晴sister
*
* https://i.csdn.net/#/uc/profile
*/
public class JobAction extends ActionSupport implements ModelDriver<Job>{
private Job job=new Job();
private JobDao jobDao=new JobDao();
@Override
public Job getModel() {
// TODO Auto-generated method stub
return job;
}
/**
* 查询所有
* @param req
* @param resp
* @return
*/
public String list(HttpServletRequest req,HttpServletResponse resp) {
PageBean pageBean =new PageBean();
pageBean.setRequest(req);
try {
List<Job> list = this.jobDao.list(job, pageBean);
req.setAttribute("jobs", list);
req.setAttribute("pageBean", pageBean);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "list";
}
public String toAdd(HttpServletRequest req,HttpServletResponse resp) {
return "toAdd";
}
public String add(HttpServletRequest req,HttpServletResponse resp) {
try {
this.jobDao.add(job);
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "toList";
}
public String toEdit(HttpServletRequest req,HttpServletResponse resp) {
try {
Job j = this.jobDao.list(job, null).get(0);
req.setAttribute("j", j);
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "toEdit";
}
public String edit(HttpServletRequest req,HttpServletResponse resp) {
try {
this.jobDao.edit(job);
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "toList";
}
public String del(HttpServletRequest req,HttpServletResponse resp) {
try {
this.jobDao.del(job);
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return "toList";
}
}
mvc.xml(建模得到结果码匹配跳转相应的界面):
<?xml version="1.0" encoding="UTF-8"?>
<config>
<action path="/job" type="com.xiaoqing.web.JobAction">
<forward name="list" path="/jobList.jsp" redirect="false" />
<forward name="toAdd" path="/jobAdd.jsp" redirect="" />
<forward name="toEdit" path="/jobEdit.jsp" redirect="false" />
<forward name="toList" path="/job.action?methodName=list" redirect="" />
</action>
</config>
jobList.jsp(查询所有展示页面):
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@taglib uri="/xiaoqing" prefix="z" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.css" rel="stylesheet">
<script src="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.js"></script>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>博客展示</title>
<style type="text/css">
.page-item input {
padding: 0;
width: 40px;
height: 100%;
text-align: center;
margin: 0 6px;
}
.page-item input,
.page-item b {
line-height: 38px;
float: left;
font-weight: 400;
}
.page-item.go-input {
margin: 0 10px;
}
</style>
</head>
<body>
<form class="form-inline" action="${pageContext.request.contextPath }/job.action?methodName=list" method="post">
<div class="form-group mb-2">
<input type="text" name="job" class="form-control-plaintext" id="staticEmail2" placeholder="请输入岗位">
</div>
<button type="submit" class="btn btn-primary mb-2">查询</button>
<!--
href="/bookAdd.jsp" 相对路径 ,相对的是Tomcat工程的根目录
href="bookAdd.jsp" 相当于请求
${pageContext.request.contextPath }/bookAdd.jsp 带上项目名的绝对路径
href="${pageContext.request.contextPath }/book.action?methodName=toAdd"通过后台转发到前台的jsp页面
-->
<a href="${pageContext.request.contextPath }/jobAdd.jsp" class="btn btn-primary mb-2 ml-4">新增</a>
</form>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">职位编号</th>
<th scope="cosl">工作岗位</th>
<th scope="col">公司名称</th>
<th scope="col">公司地址</th>
<th scope="col">薪水</th>
<th scope="col">url</th>
<th scope="col">学历</th>
<th scope="col">爬取时间</th>
<th scope="col">操作</th>
</tr>
</thead>
<tbody>
<c:forEach var="b" items="${jobs }">
<tr>
<td>${b.id }</td>
<td>${b.job }</td>
<td>${b.company }</td>
<td>${b.address }</td>
<td>${b.salary }</td>
<td><a target="_blank" href="${b.url }">${b.url }</a></td>
<td>${b.quary }</td>
<td>${b.timedate }</td>
<td>
<a href="${pageContext.request.contextPath }/job.action?methodName=del&id=${b.id}" class="btn btn-sm btn-danger btn-primary mb-2 ml-4">删除</a>
<a href="${pageContext.request.contextPath }/job.action?methodName=toEdit&id=${b.id}" class="btn btn-sm btn-success btn-primary mb-2 ml-4">修改</a>
</td>
</tr>
</c:forEach>
</tbody>
</table>
<z:page pageBean="${pageBean }"></z:page>
</body>
</html>
还有一些类没有放上来,由于小编的博客中有这些类,所以就不再重复上传啦!!这里给链接: 通用分页一. xml建模.
7.总结
自定义mvc很强大,希望大家多去理解思路!搞懂自定义mvc,十五分钟左右就可以写一个增删改查及分页查询完整版!!会省下很多的代码量,便于提高开发效率!
整个mvc就OK了
欢迎大家评论!!!
谢谢!!