作者: 永恒の_☆ 地址: http://blog.youkuaiyun.com/chenghui0317/article/details/9413401
一、什么是JavaEE MVC
JavaEE MVC 字面的意思是模型(M),视图(V),控制器(C)这三块。MVC是一种面向对象的架构模式,它的基本思想是把程序界面和业务逻辑分开,这样便于软件的后期维护,同时也方便了开发时期的分工和管理。 MVC这种开发模式已经 被直接或者间接的应用于各个web开发中,不管是JavaEE还是.NET 都被广泛运用。
二、三层开发模式和MVC 有什么区别
最开始以为三层开发模式和MVC 是一个概念,认为三层架构是MVC,MVC是三层架构。概念性的东西多少有点混淆。
其实不然,三层架构分为: 表示层,逻辑层,数据访问层。三层架构从结构来说是纵向的分层,上层依赖于下层,而下层不依赖于上层,即单项依赖。并且表示层和我们MVC的视图 都是将程序运行的结果呈现给用户,业务逻辑层和持久层是为了程序的可移植性我们把逻辑层和专门数据访问层的权限全部交给我们MVC的控制器来管理和操作。
三、MVC实现思路
JavaEE的 MVC 的思路是:加载配置文件信息在服务器启动的时候,然后用户发送的请求由一个核心控制器拦截所有的url,然后服务器根据请求的字符串拆分出控制器的实现类 以及对应的action方法,服务器根据这个控制器的实现类的完整限定名 反射得出该类的实例 ,执行该类中的action方法之后 会返回一个逻辑路径,服务器然后根据这个逻辑路径,跳转到逻辑路径对应的页面将结果显示出来。
接下来按照MVC架构模拟一个登录的功能。
首先需要一个核心控制器ActionServlet.java
@Override
public void init(ServletConfig config) throws ServletException {
//初始化 读取struts.xml
String configName = config.getInitParameter("configName");
actionMappingManager = new ActionMappingManager(configName.split(","));
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获得请求的url然后映射出请求的action
BaseAction baseAction = null;
//获得action名称
String actionPath = request.getRequestURL().toString();
String actionName = actionPath.substring(actionPath.lastIndexOf("/") + 1, actionPath.indexOf(".action"));
if(actionName!=null){
ActionMapping actionMapping = actionMappingManager.getActionMapping(actionName);
if(actionMapping!=null){
try {
Class clazz = Class.forName(actionMapping.getClassName());
baseAction = (BaseAction)clazz.newInstance();
//获得执行完 execute()返回的逻辑路径
String logicPath = baseAction.execute(request, response);
if(logicPath!=null){
String reallPath = actionMapping.getResultMap().get(logicPath).getValue();
if(actionMapping.getResultMap().get(logicPath).isRedirect()){
response.sendRedirect(reallPath);
}else{
request.getRequestDispatcher(reallPath).forward(request, response);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
在web.xml中需要配置这个核心控制器,因为是启动服务器的时候就要加载,所以具体如下:
<!-- 核心控制器 -->
<servlet>
<servlet-name>actionServlet</servlet-name>
<servlet-class>com.mvc.controller.ActionServlet</servlet-class>
<init-param>
<param-name>configName</param-name>
<param-value>/struts.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>actionServlet</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>
从上面的代码中可以看到将拆分出来的actionName 传递进去获得了一个ActionMapping 对象,这个对象的获得是先从由核心控制器在init()方法中 读取struts.xml配置文件,然后从里面获取 Action类名,Action方法,ActionResult 这样子一个一一对应的关系的actionMappings集合中,需要获取这个集合中的对应关系只需要传递一个actionName 去获取就好了,它就会返回如上面说的ActionMapping对象。ActionMapping对象获取到了之后 里面保存了action类的限定名,我们这里利用反射得到实例,然后调用action方法,根据这个方法返回的逻辑名称logicPath ,从ActionMapping对象中得到一个真实的路径,然后再从ActionMapping对象中获取本地请求的请求方式 是重定向还是请求转发,跳转后显示,本次请求完毕。
那么核心控制器在init()方法中是如何读取struts.xml中的信息呢?
配置文件的定义规范都是事先全部定义好的,然后是由dom4j 来读取xml的根目录,一层一层往里面获取节点对象以及节点的绑定值。每次解析完配置 就封装到已经定义好的ActionMapping对象中,然后将着所有的对象全部放在ActionMappings这个大的集合中。怎么实现,代码如下:
package com.mvc.controller;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.ActionMap;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
* action manager
*
* @author Administrator
*
*/
public class ActionMappingManager {
public Map<String, ActionMapping> actionMappings = new HashMap<String, ActionMapping>();
/**
* 实例化的时候就加载struts.xml
*
* @param configNames
*/
public ActionMappingManager(String[] configNames) {
for (int i = 0; i < configNames.length; i++) {
initConfig(configNames[i]);
}
}
/**
* 使用dom4j解析配置文件
* @param configName
*/
public void initConfig(String configName) {
try {
if (configName == null || configName.isEmpty()) {
throw new Exception("配置文件不能为空");
}
//获得文件流,加载配置文件
InputStream inputStream = this.getClass().getResourceAsStream(configName);
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
Iterator<Element> actionsIterator = rootElement.elements("actions").iterator();
while(actionsIterator.hasNext()){
Element actionElement = actionsIterator.next();
Iterator<Element> actionIterator = actionElement.elements("action").iterator();
//循环单个action
while(actionIterator.hasNext()){
Element signActionElement= actionIterator.next();
//一个action对应ActionMapping 对象
ActionMapping actionMapping = new ActionMapping();
actionMapping.setName(signActionElement.attributeValue("name"));
actionMapping.setClassName(signActionElement.attributeValue("class"));
Iterator<Element> resultIterator = signActionElement.elementIterator("result");
while(resultIterator.hasNext()){
Element resultElement = resultIterator.next();
//使用Result封装
Result result = new Result();
result.setName(resultElement.attributeValue("name"));
result.setValue(resultElement.getText());
result.setRedirect(Boolean.valueOf(resultElement.attributeValue("redirect")));
actionMapping.addResultMap(result.getName(),result);
}
actionMappings.put(actionMapping.getName(), actionMapping);
}
}
for (ActionMapping actionMapping : actionMappings.values()) {
System.out.println("name:"+actionMapping.getName()+" ,className:"+actionMapping.getClassName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据actionName获得ActionMapping
* @param actionName
* @return
*/
public ActionMapping getActionMapping(String actionName){
return actionMappings.get(actionName);
}
}
ActionMapping.java中 定义的name 就是我们从请求url拆分出来的actionPath,className是这个action类的 完整限定名,然后resultMap这里面保存的则是 action方法执行完毕后返回的逻辑名称对应的之后所有跳转情况的集合。
具体代码如下:
package com.mvc.controller;
import java.util.HashMap;
import java.util.Map;
/**
* struts.xml映射ActionMapping
* @author Administrator
*
*/
public class ActionMapping {
private String name;
private String className;
private Map<String, Result> resultMap = new HashMap<String, Result>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public Map<String, Result> getResultMap() {
return resultMap;
}
public void addResultMap(String name, Result result) {
this.resultMap.put(name, result);
}
}
在Result.java类中定义的是逻辑名称,实际跳转页面以及跳转的方式等等。
具体代码如下:
package com.mvc.controller;
public class Result {
private String name;
private String value;
private boolean isRedirect = false; //默认请求转发
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public boolean isRedirect() {
return isRedirect;
}
public void setRedirect(boolean isRedirect) {
this.isRedirect = isRedirect;
}
}
这样子配置之后基本可以完成所有符合规范的请求。
那么接下来一起来走一个MVC的请求。
struts.xml中的定义如下:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 定义配置文件 每一对action标签保存用户提交的请求所跳转的Action页面及返回页面的信息 -->
<struts>
<actions>
<action name="login" class="com.mvc.action.LoginAction">
<result name="success" redirect="true">loginSuccess.jsp</result>
<result name="fail">/loginFail.jsp</result>
</action>
<action name="register" class="com.mvc.action.RegisterAction">
<result name="success" redirect="true">/registerSuccess.jsp</result>
<result name="fail">/registerFail.jsp</result>
</action>
</actions>
</struts>
我们根据这个配置,就可以创建一个名称为LoginAction 的类,并且完整限定名是 com.mvc.action.LoginAction,login则是请求路径,因为没有指定具体的方法名method属性,所以我们会调用默认的方法execute(),登录操作 有成功 或者失败两种请求,在<result>标签也体现出来了,以第一个为例,success为我们上面经常提到的逻辑名称,然后后面有一个跳转的方式, 在这个逻辑名称的标签内绑定的是实际路径。
根据这个思路我们定义了LoginAction.java,具体代码如下:
package com.mvc.action;
import java.io.UnsupportedEncodingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.tomcat.jni.User;
/**
* 登录action的处理action
* @author Administrator
*
*/
public class LoginAction extends BaseAction{
@Override
public String execute(HttpServletRequest request, HttpServletResponse response) throws Exception {
String name = request.getParameter("name");
String password = request.getParameter("password");
if(name!=null && password!=null){
if(name.equals("chenghui") && password.equals("password")){
User user = new User();
request.getSession().setAttribute("user", user);
return "success";
}
}
return "fail";
}
}
它继承了BaseAction 这个基类,基类里面除了一个execute()空方法以外 什么都没有。
根据上面Action类中的代码体验接收的参数,所以视图层的话很简单了,如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">
<title>My JSP 'login.jsp' starting page</title>
</head>
<body>
<h1>用户登录</h1>
<form action="${pageContext.request.contextPath}/login.action" method="post" name="loginForm">
姓名:<input type="text" name="name" /><br/>
密码:<input type="text" name="password" /><br/>
<input type="submit" value="登录" >
</form>
</body>
</html>
这样子,基本上完成了MVC架构模式的开发。
当非常熟悉这种架构模式之后就会觉得很好很强大,不用每一次请求 都定义那么多servlet 来处理请求,而是采用核心控制器来集中处理和管理。它的好处还需在以后的开发和学习中慢慢去体会。