为了更好的学习框架的运行机制,这里开始学习框架之前,介绍一个简单的自定义的框架。
需求:
登录:id:aaa,pwd:888登录成功之后,跳转到,index.jsp页面并显示,欢迎你,aaa
注册,页面,输入用户名密码,点击注册。注册成功之后,将会跳转到登录界面。
重在了解前后的这个逻辑,所以把后天是写死的。
entity层
就一个User
package cn.itcast.entity; public class User { private String name; private String pwd; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } }
接着是到层
就一个UserDao,模拟了注册,登录,没有都是写死的
1 package cn.itcast.dao; 2 3 import cn.itcast.entity.User; 4 5 6 public class UserDao { 7 8 // 模拟登陆 9 public User login(User user){ 10 if ("tom".equals(user.getName()) && "888".equals(user.getPwd()) ){ 11 // 登陆成功 12 return user; 13 } 14 // 登陆失败 15 return null; 16 } 17 18 // 模拟注册 19 public void register(User user) { 20 System.out.println("注册成功:用户," + user.getName()); 21 } 22 }
service层还和以前一样,没有什么区别,直接使用就可以了这里也就一个UserService
1 package cn.itcast.service; 2 3 import cn.itcast.dao.UserDao; 4 import cn.itcast.entity.User; 5 6 7 public class UserService { 8 9 private UserDao ud = new UserDao(); 10 11 // 模拟登陆 12 public User login(User user){ 13 return ud.login(user); 14 } 15 16 // 模拟注册 17 public void register(User user) { 18 ud.register(user); 19 } 20 }
framewoek层,
这是手动缩写的这个,mystructs的核心部分。
以往在ervlet层中写servlet,反观一下,servlet层在mvc中负责control的角色:获取参数,调用service,跳转页面。无非就这三大块。
不同的页面请求提交到不同的servt,但最终从宏观上来说无非是上面的三块内容。于是我们想做到的是,值写一个servlet这里我们命名为ActionServlet,这个类的功能,就是统筹全局请求的分配,针对比如像http:localhost:8080/mystucets/login.action的请求,ActionServlet解析出login,把这个请求,交给LoginAction类处理,类似于register.action的请求交给对应的RegisterAction处理。
要想完成这种准确无误的转发关系,或者说是一种映射。那么ActionServlet要一个根据,这个根据就是一个ActionSerlet可以查找的表。我们先来看一个,上面设想的架构的时序流
1.就收请求,http:localhost:8080/mystucets/login.action,解析出login
2。转发给LoginAction处理,LoginAction调用login()方法,这个方法执行完成之后返回一个returnFlag标志,loginSuccess表示登陆成功,loginFial表示登录失败,
3.ActionServlet根据返回的returnFlag判断跳转的页面,当然要先查询配置(映射表)文件,获取对应的跳转page,以及跳转的方式(转发或者重定向)
我们需要一个配置文件,这个配置文件是一个xml文件,这个文件我们根据,login或者register找到所对应的Action类,进一步找到跳转的可能,设计这个mystructs.xml的配置文件如下:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <mystruts> 3 <package> 4 <!-- 配置请求路径,与处理action类的关系 --> 5 <!-- 6 1. 请求路径与处理Action的关系 7 /login = LoginAction login 8 success = /index.jsp 登陆成功(重定向) 9 loginFaild = /login.jsp 登陆失败 10 11 --> 12 <action name="login" class="cn.itcast.framework.action.LoginAction" method="login"> 13 <result name="loginSuccess" type="redirect">/index.jsp</result> 14 <result name="loginFaild">/login.jsp</result> 15 </action> 16 17 <action name="register" class="cn.itcast.framework.action.RegisterAction" method="register"> 18 <result name="registerSuccess">/login</result> 19 </action> 20 21 </package> 22 23 </mystruts>
对于一个项目而言,要处理的请求有很多,也就说有很多的XxxAction,这是需要我们,那么对应的mystructs.xml文件就会很大,我们不会每次都来查找解析这个xml文件,而且对于需要同时管理很多信息时,常见的做法是把这若干信息封装到一些bean中,讲分页的时候已经使用过这种策略了。每个<action>封装到对应的ActionMapping类中,每个<result>封装到Result类中,因为每个<action>中包含若干的<result>所以每个ActionMapping总包含若干的<Result>。ActionMapping和Result的设计如下:
ActionMapping:
1 package cn.itcast.framework.bean; 2 3 import java.util.Map; 4 5 /** 6 * 封装action节点 7 * <action name="login" class="cn.itcast.framework.action.LoginAction" method="login"> 8 <result name="success" type="redirect">/index.jsp</result> 9 <result name="loginFaild">/login.jsp</result> 10 </action> 11 12 * @author Jie.Yuan 13 * 14 */ 15 public class ActionMapping { 16 17 // 请求路径名称 18 private String name; 19 // 处理aciton类的全名 20 private String className; 21 // 处理方法 22 private String method; 23 // 结果视图集合 24 private Map<String,Result> results; 25 26 public String getName() { 27 return name; 28 } 29 public void setName(String name) { 30 this.name = name; 31 } 32 public String getClassName() { 33 return className; 34 } 35 public void setClassName(String className) { 36 this.className = className; 37 } 38 public String getMethod() { 39 return method; 40 } 41 public void setMethod(String method) { 42 this.method = method; 43 } 44 public Map<String, Result> getResults() { 45 return results; 46 } 47 public void setResults(Map<String, Result> results) { 48 this.results = results; 49 } 50 51 52 53 }
Result:
1 package cn.itcast.framework.bean; 2 3 /** 4 * 封装结果视图 5 * <result name="success" type="redirect">/index.jsp</result> 6 * @author Jie.Yuan 7 * 8 */ 9 public class Result { 10 11 // 跳转的结果标记 12 private String name; 13 // 跳转类型,默认为转发; "redirect"为重定向 14 private String type; 15 // 跳转的页面 16 private String page; 17 public String getName() { 18 return name; 19 } 20 public void setName(String name) { 21 this.name = name; 22 } 23 public String getType() { 24 return type; 25 } 26 public void setType(String type) { 27 this.type = type; 28 } 29 public String getPage() { 30 return page; 31 } 32 public void setPage(String page) { 33 this.page = page; 34 } 35 36 }
为了避免每次查询mystructs.xml问价(这样使很消耗时间的),我们的做法是在首次访问ActionServlet的时候,也就是在ActionServlet的init()函数中把mystructs.xml中的内容全部读取出来(在后面的ActionServlet的代码可以看到),读取出来之后我们使用一个ActionMappingManager进行管理,下面是ActionMappingManager类:
1 package cn.itcast.framework.bean; 2 3 import java.io.InputStream; 4 import java.util.HashMap; 5 import java.util.Iterator; 6 import java.util.List; 7 import java.util.Map; 8 9 import org.dom4j.Document; 10 import org.dom4j.Element; 11 import org.dom4j.io.SAXReader; 12 13 /** 14 * 加载配置文件, 封装所有的真个mystruts.xml 15 * @author Jie.Yuan 16 * 17 */ 18 public class ActionMappingManager { 19 20 // 保存action的集合 21 private Map<String,ActionMapping> allActions ; 22 23 public ActionMappingManager(){ 24 allActions = new HashMap<String,ActionMapping>(); 25 // 初始化 26 this.init(); 27 } 28 29 /** 30 * 根据请求路径名称,返回Action的映射对象 31 * @param actionName 当前请求路径 32 * @return 返回配置文件中代表action节点的AcitonMapping对象 33 */ 34 public ActionMapping getActionMapping(String actionName) { 35 if (actionName == null) { 36 throw new RuntimeException("传入参数有误,请查看struts.xml配置的路径。"); 37 } 38 39 ActionMapping actionMapping = allActions.get(actionName); 40 if (actionMapping == null) { 41 throw new RuntimeException("路径在struts.xml中找不到,请检查"); 42 } 43 return actionMapping; 44 } 45 46 // 初始化allActions集合 47 private void init() { 48 /********DOM4J读取配置文件***********/ 49 try { 50 // 1. 得到解析器 51 SAXReader reader = new SAXReader(); 52 // 得到src/mystruts.xml 文件流 53 InputStream inStream = this.getClass().getResourceAsStream("/mystruts.xml"); 54 // 2. 加载文件 55 Document doc = reader.read(inStream); 56 57 // 3. 获取根 58 Element root = doc.getRootElement(); 59 60 // 4. 得到package节点 61 Element ele_package = root.element("package"); 62 63 // 5. 得到package节点下, 所有的action子节点 64 List<Element> listAction = ele_package.elements("action"); 65 66 // 6.遍历 ,封装 67 for (Element ele_action : listAction) { 68 // 6.1 封装一个ActionMapping对象 69 ActionMapping actionMapping = new ActionMapping(); 70 actionMapping.setName(ele_action.attributeValue("name")); 71 actionMapping.setClassName(ele_action.attributeValue("class")); 72 actionMapping.setMethod(ele_action.attributeValue("method")); 73 74 // 6.2 封装当前aciton节点下所有的结果视图 75 Map<String,Result> results = new HashMap<String, Result>(); 76 77 // 得到当前action节点下所有的result子节点 78 Iterator<Element> it = ele_action.elementIterator("result"); 79 while (it.hasNext()) { 80 // 当前迭代的每一个元素都是 <result...> 81 Element ele_result = it.next(); 82 83 // 封装对象 84 Result res = new Result(); 85 res.setName(ele_result.attributeValue("name")); 86 res.setType(ele_result.attributeValue("type")); 87 res.setPage(ele_result.getTextTrim()); 88 89 // 添加到集合 90 results.put(res.getName(), res); 91 } 92 93 // 设置到actionMapping中 94 actionMapping.setResults(results); 95 96 // 6.x actionMapping添加到map集合 97 allActions.put(actionMapping.getName(), actionMapping); 98 } 99 100 101 } catch (Exception e) { 102 throw new RuntimeException("启动时候初始化错误",e); 103 } 104 } 105 }
我们说了,ActionServlet位居中枢相当于行军主将,居中调遣需要冲锋陷阵的士卒(XxxAction),这里就是LoginAction和RegisterAction,下面是他们的定义:
LoginAction:
1 package cn.itcast.framework.action; 2 3 import java.io.IOException; 4 5 import javax.servlet.ServletException; 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 9 import cn.itcast.entity.User; 10 import cn.itcast.service.UserService; 11 12 /** 13 * Action表示动作类 1. 一个servlet对应一个action 2. action中负责处理具体的请求 14 * 15 * @author Jie.Yuan 16 * 17 */ 18 public class LoginAction { 19 20 public Object execute(HttpServletRequest request, HttpServletResponse response) 21 throws ServletException, IOException { 22 return null; 23 } 24 25 /** 26 * 处理登陆请求 27 */ 28 public Object login(HttpServletRequest request, HttpServletResponse response) 29 throws ServletException, IOException { 30 Object uri = null; 31 32 // 1. 获取请求数据,封装 33 String name = request.getParameter("name"); 34 String pwd = request.getParameter("pwd"); 35 User user = new User(); 36 user.setName(name); 37 user.setPwd(pwd); 38 39 // 2. 调用Service 40 UserService userService = new UserService(); 41 User userInfo = userService.login(user); 42 // 3. 跳转 43 if (userInfo == null) { 44 // 登陆失败 45 // request.getRequestDispatcher("/login.jsp").forward(request, 46 // response); 47 // uri = request.getRequestDispatcher("/login.jsp"); 48 uri = "loginFaild"; // loginFaild = /login.jsp 49 } else { 50 // 登陆成功 51 request.getSession().setAttribute("userInfo", userInfo); 52 // 首页 53 // response.sendRedirect(request.getContextPath() + "/index.jsp"); 54 // uri = "/index.jsp"; 55 uri = "loginSuccess"; // loginSuccess = /index.jsp 56 } 57 return uri; 58 } 59 }
Register类:
package cn.itcast.framework.action; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import cn.itcast.entity.User; import cn.itcast.service.UserService; public class RegisterAction { public Object register(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Object uri; // 1. 获取请求数据,封装 String name = request.getParameter("name"); String pwd = request.getParameter("pwd"); User user = new User(); user.setName(name); user.setPwd(pwd); // 2. 调用Service UserService userService = new UserService(); userService.register(user); // 3. 跳转 // request.getRequestDispatcher("/login.jsp").forward(request, response); //uri = request.getRequestDispatcher("/login.jsp"); return "registerSuccess"; //返回注册的标记; registerSuccess = /login.jsp } }
中军主帅ActionServlet类:
1 package cn.itcast.framework; 2 3 import java.io.IOException; 4 import java.lang.reflect.Method; 5 6 import javax.servlet.ServletException; 7 import javax.servlet.http.HttpServlet; 8 import javax.servlet.http.HttpServletRequest; 9 import javax.servlet.http.HttpServletResponse; 10 11 import cn.itcast.framework.bean.ActionMapping; 12 import cn.itcast.framework.bean.ActionMappingManager; 13 import cn.itcast.framework.bean.Result; 14 15 /** 16 * 核心控制器,此项目只有这一个servlet 17 * 1. 拦截所有的*.action为后缀的请求 18 * 2. 请求:http://localhost:8080/mystruts/login.action 19 * http://localhost:8080/mystruts/register.action 20 21 * @author Jie.Yuan 22 * 23 */ 24 public class ActionServlet extends HttpServlet{ 25 26 private ActionMappingManager actionMappingManager; 27 28 // 只执行一次 (希望启动时候执行) 29 @Override 30 public void init() throws ServletException { 31 System.out.println("1111111111111111ActionServlet.init()"); 32 actionMappingManager = new ActionMappingManager(); 33 } 34 35 // http://localhost:8080/mystruts/login.action 36 @Override 37 protected void doGet(HttpServletRequest request, HttpServletResponse response) 38 throws ServletException, IOException { 39 40 try { 41 // 1. 获取请求uri, 得到请求路径名称 【login】 42 String uri = request.getRequestURI(); 43 // 得到 login 44 String actionName=uri.substring(uri.lastIndexOf("/")+1, uri.indexOf(".action")); 45 46 // 2. 根据路径名称,读取配置文件,得到类的全名 【cn..action.LoginAction】 47 ActionMapping actionMapping = actionMappingManager.getActionMapping(actionName); 48 String className = actionMapping.getClassName(); 49 50 // 当前请求的处理方法 【method="login"】 51 String method = actionMapping.getMethod(); 52 53 // 3. 反射: 创建对象,调用方法; 获取方法返回的标记 54 Class<?> clazz = Class.forName(className); 55 Object obj = clazz.newInstance(); //LoginAction loginAction = new LoginAction(); 56 Method m = clazz.getDeclaredMethod(method, HttpServletRequest.class,HttpServletResponse.class ); 57 // 调用方法返回的标记 58 String returnFlag = (String) m.invoke(obj, request, response); 59 60 // 4. 拿到标记,读取配置文件得到标记对应的页面 、 跳转类型 61 Result result = actionMapping.getResults().get(returnFlag); 62 // 类型 63 String type = result.getType(); 64 // 页面 65 String page = result.getPage(); 66 67 // 跳转 68 if ("redirect".equals(type)) { 69 response.sendRedirect(request.getContextPath() + page); 70 } else { 71 request.getRequestDispatcher(page).forward(request, response); 72 } 73 } catch (Exception e) { 74 e.printStackTrace(); 75 } 76 } 77 78 79 @Override 80 protected void doPost(HttpServletRequest req, HttpServletResponse resp) 81 throws ServletException, IOException { 82 doGet(req, resp); 83 } 84 }
注意了,这里只有一个ActionServlet处理所有的页面请求,将处理所有的http:localhost:8080/mystructs/*.action请求,那么就要在web.xml中配置ActionServlet的<pattern-url>时使用模糊匹配具体如下:
1 <!-- 核心控制器 --> 2 <servlet> 3 <servlet-name>ActionServlet</servlet-name> 4 <servlet-class>cn.itcast.framework.ActionServlet</servlet-class> 5 <!-- 启动时候执行servlet初始化方法 --> 6 <load-on-startup>1</load-on-startup> 7 </servlet> 8 <servlet-mapping> 9 <servlet-name>ActionServlet</servlet-name> 10 <url-pattern>*.action</url-pattern> 11 </servlet-mapping>
为了保证启动时执行ActionServlet的init()方法,还用了一个参数<load-on-startup>1</load-on-startup>,使用这个参数,init函数将会在启动服务器之后调用,不然的第一次访问ActionServlet,ActionServlet创建对象时才会执行init函数。
到这里,这个简单的模拟项目就结束了,就可以开始测试了。我们访问http://localhost:8080/mystructs/login.jsp,然后输入用户名aaa和密码888,点击登录于是请求login.action,然后跳转到index.jsp页面看到欢迎你,aaa