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.java
,HandlerMapping.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.java
,LoginController
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!