• 自定义MVC


    目录

    一、什么是MVC?

    二、自定义mvc实现

    2.1中央处理器(请求转发给子控制器,然后子控制器将需要处理的参数给中央控制器)

    三、实现子控制器

    3.1定义一个Action接口

    3.2处理参数的接口:

    3.3定义一个抽象类

    3.4中央控制器(请求分发功能)

    四、mvc框架增删改查的封装

    4.1查询通用

    4.2怎么调用查询通用方法呢?

    .4.3增删改通用

    4.4怎么调用增删改通用方法呢?(无返回,就不测试了,直接数据库返回影响行数)


    我们先来看看今天的思维导图,今天给友友们分享自定义mvc实现过程的demo,希望对大家有帮助。

     

     自定义mvc框架原理图如下:

     

    核心组件说明:

    • 中央控制器(ActionServlet): 负责接收所有的请求,并分别给控制器具体处理。
    • 子控制器(Action):负责处理中央处理器分配的请求
    • 视图(view): jsp页面,负责显示
    • 模型(Model): 负责业务处理逻辑

    一、什么是MVC?

    用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑,简单来说,就是一种软件设计典范

    为什么我们要学习mvc呢?因为它提高了程序的可维护性可移植性可扩展性与可重用性,降低了程序的开发难度。它主要分模型视图控制器三层。

    优势:

    • 模型(model): 它是应用程序的主体部分,主要包括业务逻辑模块(web项目中的dao类)和数据模块(pojo类)。pojo一般可以叫做实体域模型,dao和service称为过程域模型。
    • 视图(view): 用户与之交互的界面、在web中视图一般由jsp,html组成,其他的还包括android,ios等等。
    • 控制器(controller): 接收来自界面的请求 并交给模型进行处理 在这个过程中控制器不做任何处理只是起到了一个连接的做用。

    劣势:

    • 增加系统结构和实现的复杂性。对于简单的界面,严格遵守MVC,需要使模型、视图与控制器分离,增加系统复杂性
    • 视图和控制器之间的关系太过紧密了

    二、自定义mvc实现

    为什么要存在模型层

    目的是为了方便对xml配置文件进行建模

    注意:在项目的src目录下加入如下配置文件(config.xml)

    1. config[
    2. config (action*)>
    3. action (forward*)>
    4. forward EMPTY>
    5. action
    6. path CDATA #REQUIRED
    7. type CDATA #REQUIRED
    8. >
    9. forward
    10. name CDATA #REQUIRED
    11. path CDATA #REQUIRED
    12. redirect (true|false) "false"
    13. >
    14. ]>
    15. <config>
    16. <action path="/bookAction" type="com.zking.action.BookAction">
    17. <forward name="test" path="/test.jsp" redirect="false"/>
    18. action>
    19. <action path="/studentAction" type="com.zking.action.StudentAction">
    20. <forward name="test2" path="/test2.jsp" redirect="false"/>
    21. action>
    22. config>

    xml文件解析:

    action:子控制器;path属性子控制器名,type属性子控制器全路径

    forward:重定向跳转;path属性跳转的页面路径,name跳转的页面

    为方便解析xml,这里举例三个模型

    ActionModel模型层

    1. /**
    2. * ActionModel模型
    3. */
    4. public class ActionModel {
    5. private String path;
    6. private String type;
    7. //正则规则
    8. private static Pattern pattern = Pattern.compile("^/.+$");
    9. private Map forwardMap = new HashMap<>();
    10. public String getPath() {
    11. return path;
    12. }
    13. public void setPath(String path) {
    14. checkPath(path);
    15. this.path = path;
    16. }
    17. public String getType() {
    18. return type;
    19. }
    20. public void setType(String type) {
    21. this.type = type;
    22. }
    23. //获取xml里forward的name,然后保存到集合中的方法
    24. public void put(ForwardModel forward) {
    25. if(forwardMap.containsKey(forward.getName())) {//如果xml中forward的name已存在,就不能添加
    26. throw new ForwardDuplicateDefinitionException("forward name:"+forward.getName()+"不能重复");
    27. }
    28. forwardMap.put(forward.getName(), forward);
    29. }
    30. //获取xml里forward的name值的方法
    31. public ForwardModel find(String name) {
    32. if(!forwardMap.containsKey(name)) {//xml中如果forward里面不包含name报异常
    33. throw new ForwardNotFoundException("forward name:"+name+"不存在");
    34. }
    35. return forwardMap.get(name);
    36. }
    37. @Override
    38. public String toString() {
    39. return "ActionModel [path=" + path + ", type=" + type + "]";
    40. }
    41. //严格规定path必须以/开头的方法
    42. private void checkPath(String path) {
    43. Matcher matcher = pattern.matcher(path);
    44. boolean b = matcher.matches();
    45. if (!b) {
    46. throw new InvalidPathException("ForwardModel.path[" + path + "]必须以/开头");
    47. }
    48. }
    49. }

    forwardModel模型

    1. /**
    2. * ForwardModel模型
    3. */
    4. public class ForwardModel {
    5. private String name;
    6. private String path;
    7. private boolean redirect;
    8. private static Pattern pattern = Pattern.compile("^/.+$");
    9. public String getName() {
    10. return name;
    11. }
    12. public void setName(String name) {
    13. this.name = name;
    14. }
    15. public String getPath() {
    16. return path;
    17. }
    18. public void setPath(String path) {
    19. checkPath(path);
    20. this.path = path;
    21. }
    22. public boolean isRedirect() {
    23. return redirect;
    24. }
    25. public void setRedirect(boolean redirect) {
    26. this.redirect = redirect;
    27. }
    28. public void setRedirect(String redirect) {
    29. if("true".equals(redirect)
    30. || "false".equals(redirect)) {
    31. this.redirect = Boolean.valueOf(redirect);
    32. } else {
    33. throw new RuntimeException("属性redirect的值必须位ture或者false");
    34. }
    35. }
    36. @Override
    37. public String toString() {
    38. return "ForwardModel [name=" + name + ", path=" + path + ", redirect=" + redirect + "]";
    39. }
    40. private void checkPath(String path) {
    41. Matcher matcher = pattern.matcher(path);
    42. boolean b = matcher.matches();
    43. if (!b) {
    44. throw new InvalidPathException("ForwardModel.path[" + path + "]必须以/开头");
    45. }
    46. }
    47. }

     configModel模型

    1. /**
    2. * configModel模型
    3. */
    4. public class ConfigModel {
    5. private Map actionMap = new HashMap<>();
    6. //获取xml里action的name,然后保存到集合中的方法
    7. public void put(ActionModel action) {
    8. if(actionMap.containsKey(action.getPath())) {如果xml中action的name已存在,就不能添加
    9. throw new ActionDuplicateDefinitionException("action path:" + action.getPath()+ " 不能重复");
    10. }
    11. actionMap.put(action.getPath(), action);
    12. }
    13. //获取xml里action的name值的方法
    14. public ActionModel find(String path) {
    15. if(! actionMap.containsKey(path)) {//xml中如果action里面不包含name报异常
    16. throw new ActionNotFoundException("action path:"+path+"没有找到");
    17. }
    18. return actionMap.get(path);
    19. }
    20. }

    2.1中央处理器(请求转发给子控制器,然后子控制器将需要处理的参数给中央控制器)

    通过servlet来实现一个中央控制器,负责所有请求的接收。

    • 编写简单的请求分发实现功能
    • 实现通过配置文件来配置子控制器的功能
    • 完善请求参数处理功能
    1. /**
    2. * 中央处理器,负责接受所有的请求,转发给子控制器
    3. */
    4. @WebServlet("*.action")
    5. public class ActionDispatchServlet extends HttpServlet {
    6. private static ConfigModel configModel= ConfigModelFactory.getConfig();
    7. @Override
    8. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    9. doPost(req, resp);
    10. }
    11. @Override
    12. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    13. String servletPath = req.getServletPath();
    14. System.out.println("分割前="+servletPath);
    15. String path = servletPath.split("\\.")[0];//转义
    16. System.out.println("分割后="+path);
    17. Action action = getActionByPath(path);
    18. //判断Action需要进行参数处理
    19. if(action instanceof ModelDrive) {
    20. ModelDrive model = (ModelDrive)action;
    21. Object obj = model.getModel();
    22. try {
    23. BeanUtils.populate(obj, req.getParameterMap());
    24. } catch (IllegalAccessException | InvocationTargetException e) {
    25. throw new ActionDispatchSetParamterException("在中央处理器中设置参数的时候发生异常",e);
    26. }
    27. }
    28. //转发
    29. String name = action.execute(req, resp);
    30. }

    三、实现子控制器

    为方便调试,实现两个子控制器

    BookAction子控制器

    1. public class BookAction extends AbstractorAction implements ModelDrive {
    2. private Book book = new Book();
    3. @Override
    4. public Object getModel() {
    5. return book;
    6. }
    7. public String getBook(HttpServletRequest req, HttpServletResponse resp) {
    8. System.out.println("Book Action getBook id=" + book.getBid());
    9. return "test";
    10. }
    11. public String addBook(HttpServletRequest req, HttpServletResponse resp) {
    12. System.out.println("Book Action addBook id=" + book.getBid());
    13. return "test";
    14. }
    15. }

     StudentAction子控制器

    1. public class StudentAction extends AbstractorAction implements ModelDrive{
    2. private Student student = new Student();
    3. @Override
    4. public Object getModel() {
    5. return student;
    6. }
    7. public String getstudent(HttpServletRequest req, HttpServletResponse resp) {
    8. System.out.println("student Action getstudent sid=" + student.getSid());
    9. return "test2";
    10. }
    11. public String addstudent(HttpServletRequest req, HttpServletResponse resp) {
    12. System.out.println("student Action addstudent sid=" + student.getSid());
    13. return "test2";
    14. }
    15. }

    Test.jsp页面

    1. <%@ page language="java" contentType="text/html; charset=UTF-8"
    2. pageEncoding="UTF-8"%>
    3. 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=UTF-8">
    7. <title>Insert title heretitle>
    8. head>
    9. <body>
    10. <h4>This my BookActionh4>
    11. body>
    12. html>

    Test2.jsp页面

    1. <%@ page language="java" contentType="text/html; charset=UTF-8"
    2. pageEncoding="UTF-8"%>
    3. 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=UTF-8">
    7. <title>Insert title heretitle>
    8. head>
    9. <body>
    10. <h4>This my StudentActionh4>
    11. body>
    12. html>

    运行结果:

    BookActon

    StudentAction

     

     

    3.1定义一个Action接口

    目的

    子控制器的接口,规定每个子控制器必须实现的方法

    1. /**
    2. * 每个子控制器必须实现该接口,负责处理中央处理器分配的请求
    3. * @author Administrator
    4. */
    5. public interface Action {
    6. /**
    7. * 处理请求
    8. * @param request 请求
    9. * @param response 响应
    10. * @return String 返回转发或重定向的jsp页面名称
    11. */
    12. String exeute(HttpServletRequest request, HttpServletResponse response);
    13. }

    3.2处理参数的接口:

    目的

    参数处理接口,实现了该接口的Action,表示需要中央控制器统一处理参数

    参数将被存放到getModel方法返回的对象中

    1. /**
    2. * 参数处理接口,实现了该接口的Action,表示需要中央控制器统一处理
    3. * 参数,参数将被存放到getModel方法返回的对象中
    4. */
    5. public interface ModelDrive {
    6. Object getModel();
    7. }

    3.3定义一个抽象类

    目的

    在提交请求的时候,做一个规定:必须要有一个参数,叫做methodName,参数的值需要处理请求的方法名、在execute再通过反射机制去进行方法调用。

    1. /**
    2. * 在提交请求的时候,做一个规定:必须要有一个参数,叫做methodName,参数的值需要处理请求的方法名
    3. * 在execute再通过反射机制去进行方法调用
    4. */
    5. public abstract class AbstractorAction implements Action {
    6. @Override
    7. public String execute(HttpServletRequest req, HttpServletResponse resp) {
    8. String methodName = req.getParameter("methodName");
    9. System.out.println(methodName);
    10. Classextends AbstractorAction> clazz = (Classextends AbstractorAction>)this.getClass();
    11. try {
    12. Method method = clazz.getDeclaredMethod(
    13. methodName,
    14. HttpServletRequest.class,
    15. HttpServletResponse.class);
    16. return (String) method.invoke(this, req,resp);
    17. } catch (Exception e) {
    18. e.printStackTrace();
    19. }
    20. return null;
    21. }
    22. }

    3.4中央控制器(请求分发功能)

    完善中央处理器,通过配置文件来获取子控制器配置,通过反射,动态的获取子控制器实例。

    为了在中央控制器中完成请求的分发,需要在中央控制器中维护所有子控制器的实例,并且能够依据请求路径,将请求转发给与其关联的子控制器。

    1. /**
    2. * 中央处理器,负责接受所有的请求,转发给子控制器
    3. */
    4. @WebServlet("*.action")
    5. public class ActionDispatchServlet extends HttpServlet {
    6. private static ConfigModel configModel= ConfigModelFactory.getConfig();
    7. @Override
    8. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    9. doPost(req, resp);
    10. }
    11. @Override
    12. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    13. String servletPath = req.getServletPath();
    14. System.out.println("分割前="+servletPath);
    15. String path = servletPath.split("\\.")[0];//转义
    16. System.out.println("分割后="+path);
    17. Action action = getActionByPath(path);
    18. //判断Action需要进行参数处理
    19. if(action instanceof ModelDrive) {
    20. ModelDrive model = (ModelDrive)action;
    21. Object obj = model.getModel();
    22. try {
    23. BeanUtils.populate(obj, req.getParameterMap());
    24. } catch (IllegalAccessException | InvocationTargetException e) {
    25. throw new ActionDispatchSetParamterException("在中央处理器中设置参数的时候发生异常",e);
    26. }
    27. }
    28. //转发
    29. String name = action.execute(req, resp);
    30. //处理ajax请求
    31. if(name == null || "".equals(name)) {
    32. return;
    33. }
    34. ForwardModel fm = configModel.find(path).find(name);
    35. if(fm.isRedirect()) {
    36. resp.sendRedirect(fm.getPath());
    37. } else {
    38. req.getRequestDispatcher(fm.getPath()).forward(req, resp);
    39. }
    40. }
    41. //使用反射机制获取Action实例
    42. private Action getActionByPath(String path) {
    43. ActionModel am = configModel.find(path);
    44. String tmp = am.getType();
    45. Action action = null;
    46. try {
    47. Class actionClass = (Class)Class.forName(tmp);
    48. action = actionClass.newInstance();
    49. } catch (Exception e) {
    50. e.printStackTrace();
    51. }
    52. return action;
    53. }
    54. }

    四、mvc框架增删改查的封装

    4.1查询通用

    带分页的查询

    1. /**
    2. * 执行查询,分页,没有条件
    3. * @param sql 查询语句
    4. * @param pageBean 分页条件
    5. * @param clazz 存放数据的实体类型
    6. * @return list
    7. */
    8. public static List query(String sql,
    9. PageBean pageBean,
    10. Class clazz) {
    11. return query(sql, new Object[]{}, pageBean, clazz);
    12. }

    不分页查询 

    1. /**
    2. * 执行查询,不分页
    3. * @param sql sql语句
    4. * @param args 查询参数,对象数组
    5. * @param clazz 数据记录对应的实体对象类型
    6. * @return list
    7. */
    8. public static List query(String sql,
    9. Object[] args,
    10. Class clazz) {
    11. return query(sql, args, null, clazz);
    12. }

     具体实现方法

    1. /**
    2. * 分页查询功能
    3. * @param sql sql语句
    4. * @param args 查询参数,对象数组
    5. * @param pageBean 分页对象
    6. * @param clazz 数据记录对应的实体对象类型
    7. * @return
    8. */
    9. public static List query(String sql,
    10. Object[] args,
    11. PageBean pageBean,
    12. Class clazz) {
    13. List datas = new ArrayList<>();
    14. Connection con = null;
    15. PreparedStatement ps = null;
    16. ResultSet rs = null;
    17. //如果需要分页,则统计总记录数
    18. if(pageBean != null && pageBean.isPagination()) {
    19. String sqlCount = "SELECT COUNT(*) FROM (" + sql + ") t";
    20. try {
    21. con = DBUtil.getConection();
    22. ps = con.prepareStatement(sqlCount);
    23. //设置查询参数
    24. int i = 1;
    25. for(Object arg: args) {
    26. ps.setObject(i, arg);
    27. i++;
    28. }
    29. rs = ps.executeQuery();
    30. while(rs.next()) {
    31. pageBean.setTotal(rs.getInt(1));
    32. }
    33. } catch (SQLException e) {
    34. DBUtil.closeDB(rs, ps, con);
    35. throw new RuntimeException("统计总记录数异常", e);
    36. } finally {
    37. DBUtil.closeDB(rs, ps);
    38. }
    39. if(pageBean.getTotal()== 0) {
    40. return datas;
    41. }
    42. }
    43. try {
    44. String pagingSql = sql;
    45. if(pageBean != null && pageBean.isPagination()) {
    46. pagingSql = sql + " limit "
    47. + pageBean.getStartIndex() + "," + pageBean.getRows();
    48. }
    49. con = con == null ? DBUtil.getConection() : con;
    50. ps = con.prepareStatement(pagingSql);
    51. //设置查询参数
    52. int i = 1;
    53. for(Object arg: args) {
    54. ps.setObject(i, arg);
    55. i++;
    56. }
    57. rs = ps.executeQuery();
    58. Map columnFieldMap = getColumnFieldMap(clazz);
    59. int columnNum = rs.getMetaData().getColumnCount();
    60. while(rs.next()) {
    61. E bean = clazz.newInstance();
    62. for(int index = 1; index <= columnNum; index++) {
    63. String cn = rs.getMetaData().getColumnName(index);
    64. //如果实体类中没有定义与列名对应的属性,则直接放弃
    65. if(!columnFieldMap.containsKey(cn) || rs.getObject(index) == null) continue;
    66. BeanUtils.setProperty(bean, columnFieldMap.get(cn), rs.getObject(index));
    67. }
    68. datas.add(bean);
    69. }
    70. } catch (SQLException e) {
    71. throw new QueryRecordException("查询分页数据异常", e);
    72. } catch (InstantiationException | IllegalAccessException e) {
    73. throw new QueryRecordException("查询分页数据异常", e);
    74. } catch (InvocationTargetException e) {
    75. throw new QueryRecordException("查询分页数据异常", e);
    76. } finally {
    77. DBUtil.closeDB(rs, ps, con);
    78. }
    79. return datas;
    80. }

    4.2怎么调用查询通用方法呢?

    控制台结果:

    带分页的查询

    1. @Override
    2. public List queryUser2(User u, PageBean pb) {
    3. String sql = "select id,name,loginName,pwd,rid from t_oa_user ";
    4. List param = new ArrayList();
    5. if (u !=null && !"".equals(u.getName()) && u.getName() != null ) {
    6. sql += " where name like ?";
    7. param.add(u.getName() + "%");
    8. }
    9. return DbTemplate.query(sql, param.toArray(), pb, User.class);//.toArray();集合转为数组
    10. }
    11. 不带分页的查询

      1. @Override
      2. public List listMeetingMember(){
      3. String sql="select * from t_oa_user";
      4. return DbTemplate.query(sql, MeetingMember.class);
      5. }

      .4.3增删改通用

      1. /**
      2. * 新增,更新,或删除记录
      3. * @param sql 更新sql语句
      4. * @param args 参数数组
      5. * @return int 影响的行数
      6. */
      7. public static int update(String sql, Object[] args) {
      8. Connection con = null;
      9. PreparedStatement ps = null;
      10. try {
      11. con = DBUtil.getConection();
      12. ps = con.prepareStatement(sql);
      13. int i = 1;
      14. for(Object arg: args) {
      15. ps.setObject(i, arg);
      16. i++;
      17. }
      18. return ps.executeUpdate();
      19. } catch (SQLException e) {
      20. throw new UpdateRecordException("执行:"+ sql, e);
      21. } finally {
      22. DBUtil.closeDB(null, ps, con);
      23. }
      24. }

      4.4怎么调用增删改通用方法呢?(无返回,就不测试了,直接数据库返回影响行数)

      新增示例

      1. @Override
      2. public void addUser(User u) {
      3. String sql="insert into t_oa_user(name,loginName,pwd,rid) values(?,?,?,?);";
      4. DbTemplate.update(sql, new Object[] {u.getName(),u.getLoginName(),u.getPwd(),u.getRid()});
      5. }

       修改示例

      1. @Override
      2. public void updateUser(User user) {
      3. String sql = "update t_oa_user set name=?, loginName=?, rid=? where id = ? ";
      4. DbTemplate.update(sql,new Object[]{user.getName(),user.getLoginName(),user.getRid(),user.getId()});
      5. }
    12. 相关阅读:
      认识xml文件
      【LCP 50. 宝石补给】
      PAT 1008 Elevator
      Intent-Centric是Account Abstraction的新瓶装旧酒还是进化的最优路径?
      数据结构 --- 回溯算法
      git 修改用户名和密码
      #gStore-weekly | gStore最新版本0.9.1之BIND函数的使用
      无线发射芯片解决方案在智能家居中的应用
      双赢!企业咨询行业和低代码工具的破局之路
      ipv4 网络划分,网络段与子网掩码
    13. 原文地址:https://blog.csdn.net/Bugxiu_fu/article/details/126228971