自己开发一个简单的MVC框架-SmartMVC

SmartMVC是什么?

是一个用来简化基于MVC架构的web应用程序开发框架(类似于SpringMVC)

其核心是一个通用的控制器(DispatcherServlet)

使用该框架,只需要写视图和模型。

该框架使前端控制器成为可复用的代码,主要完成转发动作,处理器(其中含有少量代码)封装具体业务逻辑。DispatchServlet:核心前端控制器,处理任何 *.do的请求,前端控制器处理全部 Web 功能。

SmartMVC架构

在这里插入图片描述

在这里插入图片描述

创建一个maven工程(war包),项目关联tomcat->Targeted Runtimes

框架的核心类全部在src/main/java下的base包中

在这里插入图片描述

框架具体代码如下:

  • pom.xml中添加dom4j依赖(用来解析xml文件)

    <dependencies>
        <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
    
    </dependencies>
    
  • web.xml中配置框架的信息

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
      <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>base.web.DispatcherServlet</servlet-class>
        <!-- 指定配置文件的名称及路径 -->
        <init-param>
        	<param-name>configLocation</param-name>
        	<param-value>smartmvc.xml</param-value>
        </init-param>
        <!-- 配置启动加载 
        	容器启动之后,会立即将这个Servlet实例化和初始化
        -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
      </servlet-mapping>
    </web-app>框架的核心类如下
    
  • src/main/resources创建smartmvc.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans>
    	<!-- 配置处理器 class属性用于指定处理器类名 -->
    	<bean class="controller.HelloController" />
    	<bean class="controller.LoginController" />
    </beans>
    

该框架的所有代码全部在src/main/java下的base包中

  • base包下新建annotation包,该包有一个类RequestMapping.java,自定义@RequestMapping注解

    package base.annotation;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 开发RequestMapping注解
     * @Retention 是一个元注解(即由系统提供,专门用来解释其他的注解的注解)
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RequestMapping {
    	/*
    	 * value是注解的属性(不是方法),类型是String
    	 * 如果注解名为value,且只有一个属性,则该注解在使用时,不需要写属性名
    	 */
    	public String value();
    }
    
    
  • base包下新建common包,该包有两个类Handler.javaHandlerMapping.java

    Handler.java

    package base.common;
    
    import java.lang.reflect.Method;
    
    /**
     * 为了方便利用java反射机制去调用处理器的方法而设计的一个辅助类 
     * obj:处理器实例 
     * mh:处理器方法所对应的Method对象
     * (mh.invoke(obj))
     */
    public class Handler {
    	private Object obj;
    	private Method mh;
    
    	public Object getObj() {
    		return obj;
    	}
    
    	public void setObj(Object obj) {
    		this.obj = obj;
    	}
    
    	public Method getMh() {
    		return mh;
    	}
    
    	public void setMh(Method mh) {
    		this.mh = mh;
    	}
    
    }
    
    

    HandlerMapping.java

    package base.common;
    
    import java.lang.reflect.Method;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import base.annotation.RequestMapping;
    
    /**
     * 映射处理器类:
     * 负责提供请求路径与处理器及方法的对应关系
     * (如:请求路径为"/hello.do",由HelloController的hello方法来处理)
     */
    public class HandlerMapping {
    	// handlerMap存放有请求路径与处理器及方法的的对应关系(Handler是处理器及方法的封装)
    	private Map<String,Handler> handlerMap = new HashMap<>();
    	
    	/**
    	 * 依据请求路径返回对应的Handler对象
    	 * @param path 请求路径
    	 * @return 对应的Handler对象
    	 */
    	public Handler getHandler(String path) {
    		return handlerMap.get(path);
    	}
    	
    	/**
    	 * 负责建立请求路径与处理器及方法的的对应关系
    	 * @param beans 处理器实例组成的集合
    	 */
    	public void process(List beans) {
    		for (Object bean : beans) {
    			// 获取加载处理器类名前的@RequestMapping()注解(注解是可选的,可能没有,则得到的root为null)
    			RequestMapping root = bean.getClass().getAnnotation(RequestMapping.class);
    			// 加在类名前的注解路径
    			String rootPath = "";
    			if(root != null) {
    				// 获取根注解(类名前)的请求路径
    				rootPath = root.value();
    				System.out.println("rootPath:"+rootPath);
    			}
    			// 获得处理器的所有方法
    			Method[] methods = bean.getClass().getDeclaredMethods();
    			// 遍历这些方法
    			for(Method mh : methods) {
    				// 获得加在方法前的@RequestMapping注解
    				RequestMapping rm = mh.getAnnotation(RequestMapping.class);
    				// 处理器方法有可能没有添加@RequestMapping注解
    				if(rm != null) {
    					// 获取请求路径
    					String path = rm.value();
    					System.out.println("路径:"+path);
    					// 将处理器实例及方法对象封装成Handler对象
    					Handler handler = new Handler();
    					handler.setObj(bean);
    					handler.setMh(mh);
    					// 将请求路径与处理器及方法的的对应关系存放入map
    					handlerMap.put(rootPath+path, handler);
    				}
    			}
    		}
    		System.out.println("handlerMap:"+handlerMap);
    	}
    }
    
    
  • base包下新建web包,该包中有一个类DispatherServlet.java,控制器类

    package base.web;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.dom4j.Document;
    import org.dom4j.Element;
    import org.dom4j.io.SAXReader;
    
    import base.common.Handler;
    import base.common.HandlerMapping;
    
    /**
     * 
     */
    public class DispatcherServlet extends HttpServlet {
    	private static final long serialVersionUID = 1L;
    	
    	private HandlerMapping handlerMapping; // 请求路径与处理器及方法的对应关系
    	
    
    	/**
    	 * 1.读取smartmvc的配置文件,比如(smartmvc.xml),获得处理器的类名 
    	 * 2.将处理器实例化
    	 * 3.将处理器实例交给HandlerMapping来处理
    	 */
    	@Override
    	public void init() throws ServletException {
    		try {
    			// 1.创建SAXReader
    			SAXReader reader = new SAXReader();
    			
    			/*
    			 *  读取配置文件名及路径(读取初始化参数)
    			 *  getInitParameter方法来自于GenericServlet
    			 *  GenericServlet是HttpServlet的父类
    			 */
    			String configLocation = getInitParameter("configLocation");
    			
    			// 2.使用SAXReader读取要解析的XML文档
    			// 构造一个指向配置文件输入流
    			InputStream in = getClass().getClassLoader().getResourceAsStream(configLocation);
    
    			Document doc = reader.read(in);
    			// 3.通过Document获取根元素
    			Element root = doc.getRootElement();
    			// 4.按照XML文档的结构逐级获取子元素达到遍历XML文档的目的
    			List<Element> list = root.elements();
    			// 创建集合beans,用于存放处理器实例
    			List beans = new ArrayList<>();
    			for(Element ele : list) {
    				// 获得处理器类名
    				String className = ele.attributeValue("class");
    				System.out.println("className:"+className);
    				// 将处理器实例化
    				Object obj = Class.forName(className).newInstance();
    				// 为了方便管理,将其添加入集合beans中
    				beans.add(obj);
    				
    			}
    			System.out.println("beans:"+beans);
    			// 创建映射处理器实例
    			handlerMapping = new HandlerMapping();
    			// 将处理器实体交给映射处理器来处理
    			handlerMapping.process(beans);
    		} catch (Exception e) {
    			System.out.println("初始化失败:" + e);
    		}
    
    	}
    
    	protected void service(HttpServletRequest request, HttpServletResponse response)
    			throws ServletException, IOException {
    		// 设置字符集编码
    		request.setCharacterEncoding("utf-8");
    		// 获得请求资源路径(如:/smartmvc/login.do)
    		String uri = request.getRequestURI();
    		System.out.println("uri:"+uri);
    		// 获得应用名(如:/smartmvc)
    		String contextPath = request.getContextPath();
    		System.out.println("contextPath:"+contextPath);
    		// 将请求资源路径中的应用名截取掉(如:/login.do)
    		String path = uri.substring(contextPath.length());
    		System.out.println("path:"+path);
    		// 依据请求路径来获得相应的Handler对象
    		Handler handler = handlerMapping.getHandler(path);
    		System.out.println("handler:"+handler);
    		if(handler == null) {
    			// 没有对应的处理器
    			System.out.println("与请求路径"+path+"没有对应的处理器!");
    			response.sendError(404);
    			return;
    		}else {
    			// 有对应的处理器,获得处理器实例 
    			Object obj = handler.getObj();
    			// 获得处理器方法所对应的Method对象
    			Method mh = handler.getMh(); 
    			// 处理器方法的返回值
    			Object rv = null;
    			try {
    				// 调用处理器的方法
    				// 先活动处理器方法的参数类型信息(看有无参数)
    				Class[] types = mh.getParameterTypes();
    				if(types.length > 0) {
    					// 带参数,params用于存放实际参数值
    					Object[] params = new Object[types.length];
    					// 根据参数类型进行相应的赋值
    					for(int i=0;i<types.length;i++) {
    						// 这里我们设计暂时只支持request,response
    						if(types[i] == HttpServletRequest.class) {
    							params[i] = request;
    						}
    						if(types[i] == HttpServletResponse.class) {
    							params[i] = response;
    						}
    						// 目前SmartMVC只支持两种类型,以后可以在此扩展
    					}
    					rv = mh.invoke(obj,params);
    				}else {
    					// 处理器的方法不带参
    					// 调用处理器的方法
    					rv = mh.invoke(obj);
    				}
    				// 获得视图名
    				String viewName = rv.toString();
    				System.out.println("viewName:"+viewName);
    				/*
    				 *  处理视图名.
    				 *  如果视图名是以"redirect:"开头,则重定向,
    				 *  否则 转发到"/WEB-INF/"+视图名+".jsp"
    				 */
    				if(viewName.startsWith("redirect:")) {
    					// 生成重定向地址(使用绝对路径)
    					String redirectPath = contextPath + "/" 
    							+ viewName.substring("redirect:".length());
    					// 重定向
    					response.sendRedirect(redirectPath);
    				}else {
    					// 生成转发的地址
    					String forwardPath = "/WEB-INF/" + viewName + ".jsp";
    					// 转发
    					request.getRequestDispatcher(forwardPath).forward(request, response);
    				}
    				
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    

    至此,框架的核心代码完成!


  • 测试使用。在src/main/java下新建controller包,包中有两个类HelloController.javaLoginController

    package controller;
    
    import base.annotation.RequestMapping;
    
    /**
     * 这是一个处理器类,是负责业务逻辑的处理,也可以调用其他类一起处理业务逻辑
     */
    public class HelloController {
    	@RequestMapping("/hello.do")
    	public String hello() {
    		System.out.println("HelloController.hello()");
    		// 返回视图名
    		return "hello";
    	}
    }
    
    
    package controller;
    
    import javax.servlet.http.HttpServletRequest;
    
    import base.annotation.RequestMapping;
    
    @RequestMapping("/demo")
    public class LoginController {
    	@RequestMapping("/toLogin.do")
    	public String toLogin() {
    		System.out.println("LoginController.toLogin()");
    		// 返回视图名
    		return "login";
    	}
    	
    	@RequestMapping("/login.do")
    	public String login(HttpServletRequest request) {
    		System.out.println("LoginController.login()");
    		String username = request.getParameter("username");
    		String pwd = request.getParameter("pwd");
    		System.out.println("\t userinfo:"+username+","+pwd);
    		if("root".equals(username) && "1234".equals(pwd)) {
    			// 登录成功
    			// 如果视图名是以“redirect:”开头,表示重定向
    			return "redirect:demo/toWelcome.do";
    		}else {
    			// 登录失败
    			request.setAttribute("login_failed", "用户名或密码错误");
    			return "login";
    		}
    	}
    	
    	@RequestMapping("/toWelcome.do")
    	public String toWelcome() {
    		System.out.println("LoginController.toWelcome()");
    		// 返回视图名
    		return "welcome";
    	}
    }
    
    
  • src/main/webapp/WEB-INF下创建对应的jsp文件

    hello.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	Hello SmartMVC!
    </body>
    </html>
    

    login.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<form action="login.do" method="post">
    		<fieldset>
    			<legend>登录</legend>
    			用户名:<input name="username">
    			${login_failed}
    			<br>
    			密码:<input name="pwd" type="password"><br>
    			<input type="submit" value="登录">
    		</fieldset>
    	</form>
    </body>
    </html>
    

    welcome.jsp

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<h1>登录成功</h1>
    </body>
    </html>
    



如何使用SmartMVC?

新建一个Maven工程(war包),项目关联tomcat->Targeted Runtimes

  • 导包(在pom.xml中导入dom4j)

    <!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    
  • 将SmartMVC项目核心类拷贝到新的工程里面

在这里插入图片描述

  • web.xml文件中配置DispatherServlet

    示例:以下配置文件是smartmvc.xml,只能接收以*.do结尾的请求

    <servlet>
        <servlet-name>DispatcherServlet</servlet-name>
        <servlet-class>base.web.DispatcherServlet</servlet-class>
        <!-- 指定配置文件的名称及路径 -->
        <init-param>
            <param-name>configLocation</param-name>
            <param-value>smartmvc.xml</param-value>
        </init-param>
        <!-- 配置启动加载 容器启动之后,会立即将这个Servlet实例化和初始化 -->
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>DispatcherServlet</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>
    
    
  • src/main/resources下创建配置文件smartmvc.xml(跟上面指定配置文件的名称及路径一致)(文件名及路径可以自定义)

    <?xml version="1.0" encoding="UTF-8"?>
    
    <beans>
    	<!-- 配置处理器 class属性用于指定处理器类名 -->
        <!-- 示例如下
    	<bean class="controller.HelloController" />
    	-->
    </beans>
    
  • 添加jsp文件(放到/WEB-INF/下)

    示例:创建hello.jsp文件

    <%@ page contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <html>
    <head>
    	<title></title>
    </head>
    <body style="font-size:30px">
    	<h1>hello smartmvc!</h1>
    </body>
    </html>
    
  • 添加处理器

    注:

    ​ 1. 处理器类名前面或者方法前面添加@RequestMapping注解

    ​ 2. 方法要求返回一个视图名,如果视图名以“redirect:”开头,则表示重定向

    示例:在src/main/java下创建一个包controller,包下创建一个类HelloController

    package controller;
    
    import base.annotation.RequestMapping;
    
    @RequestMapping("/test")
    public class HelloController {
    	@RequestMapping("/hello.do")
    	public String hello() {
    		return "hello";
    	}
    }
    
  • 在SmartMVC的配置文件smartmvc.xml中指定处理器的类名

    <!-- 配置处理器 class属性用于指定处理器类名 -->
    <bean class="controller.HelloController" />
    
  • 测试

    开启服务:访问http://localhost:8080/smartmvc-exec/test/hello.do,可以看到hello smartmvc!

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值