MVC全名 Model View Controller,翻译:模型(model)视图(view) 控制器(controller)是一种软件的设计模仿
用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
提高了程序的可维护性、可移植性、可扩展性与可重用性,降低了程序的开发难度。它主要分模型、视图、控制器三层。
不足的地方:
核心组件说明:
通过servlet实现一个中央控制器,用来处理所有请求
package com.bin.servlet;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("*.action")
/**
*
* @author bin
* servlet 处理请求
*/
public class ActionServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// TODO Auto-generated method stub
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("action...");
}
}
转发 获取.xml的action
DOCTYPE config[
<!ELEMENT config (action*)>
<!ELEMENT action (forward*)>
<!ELEMENT forward EMPTY>
<!ATTLIST action
path CDATA #REQUIRED
type CDATA #REQUIRED
>
<!ATTLIST forward
name CDATA #REQUIRED
path CDATA #REQUIRED
redirect (true|false) "false"
>
]>
<config>
<action path="/studentAction" type="org.lisen.mvc.action.StudentAction">
<forward name="students" path="/students/studentList.jsp" redirect="false"/>
action>
config>
实现action接口
/**
* 每个子控制器必须实现该接口,负责处理中央处理器分配的请求
* @author Administrator
*/
public interface Action {
/**
* 处理请求
* @param request 请求
* @param response 响应
* @return String 返回转发或重定向的jsp页面名称
*/
String exeute(HttpServletRequest request, HttpServletResponse response);
}
为方便调试,实现两个子控制器,
public class BookAction implements Action {
@Override
public String exeute(HttpServletRequest request, HttpServletResponse response) {
return "bookList";
}
}
public class StudentAction implements Action {
@Override
public String exeute(HttpServletRequest request, HttpServletResponse response) {
// TODO Auto-generated method stub
return "students";
}
}
为了便于理解,我们可以分步骤的,循序渐进的完善中央控制器:
为了在中央控制器中完成请求的分发,需要在中央控制器中维护所有子控制器的实例,并且能够依据请求路径,将请求转发给与其关联的子控制器。
/**
* 中央控制器,负责接收所有的请求并分别给控制器具体处理
* @author Administrator
*/
@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet {
//用于保存path与action子控制器的映射
public static Map<String, Action> actionMap = new HashMap<>();
static {
actionMap.put("/studentAction", new StudentAction());
actionMap.put("/bookAction", new BookAction());
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
doPost(request, response);
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
String servletPath = request.getServletPath();
String path = servletPath.split("\\.")[0];
Action action = actionMap.get(path);
String rpath = action.exeute(request, response);
System.out.println(rpath);
}
}
在上面的示例中,在中央控制器中直接创建action子控制器,如果新增一个子控制器需要在中央控制器中添加,这样并不实用。 为了增加灵活性,可以将action转移到配置文件中配置,中央控制器通过配置来初始化action子控制器。
此时需要将config.xml文件的解析和建模项目的功能集成进来。
(ConfigModel,ActionModel,ForwardModel,ConfigModelFactory)
在项目的src目录下加入如下配置文件(config.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE config[
<!ELEMENT config (action*)>
<!ELEMENT action (forward*)>
<!ELEMENT forward EMPTY>
<!ATTLIST action
path CDATA #REQUIRED
type CDATA #REQUIRED
>
<!ATTLIST forward
name CDATA #REQUIRED
path CDATA #REQUIRED
redirect (true|false) "false"
>
]>
<config>
<action path="/studentAction" type="org.lisen.mvc.action.StudentAction">
<forward name="students" path="/students/studentList.jsp" redirect="false"/>
</action>
</config>
@WebServlet("*.action")
public class ActionDispatchServlet extends HttpServlet {
//用于保存path与action子控制器的映射
//public static Map actionMap = new HashMap<>();
private static ConfigModel configModel;
static {
//actionMap.put("/students", new StudentAction());
//actionMap.put("/books", new BookAction());
configModel = ConfigModelFactory.getConfigModel();
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
doPost(request, response);
}
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
String servletPath = request.getServletPath();
String path = servletPath.split("\\.")[0];
Action action = getActionByPath(path);
String name = action.exeute(request, response);
ForwardModel forwardModel = getForwardModel(path, name);
if (forwardModel.isRedirect()) {
response.sendRedirect(request.getContextPath() + "/" + forwardModel.getPath());
} else {
request.getRequestDispatcher(forwardModel.getPath()).forward(request, response);
}
}
//通过请求路径获取对应的action实例
private Action getActionByPath(final String path) {
ActionModel action = configModel.find(path);
try {
Class<?> clazz = Class.forName(action.getType());
return (Action)clazz.newInstance();
} catch (Exception e) {
throw new RuntimeException("创建Action实例异常"+e.getMessage(), e);
}
}
public ForwardModel getForwardModel(String path, String name) {
return configModel.find(path).find(name);
}
}
下期完善action
/**
* 对于需要处理请求参数的Action可以通过实现该接口获取请求参数的
* 处理能力,中央控制器将会使用该接口来获取Model对象,并统一处理
* 参数
* @author Administrator
*/
public interface ModelDrive {
Object getModel();
}
在中央处理器中加入请求参数的处理能力
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
......
Action action = getActionByPath(path);
//处理请求参数
if(action instanceof ModelDrive) {
Object model = ((ModelDrive) action).getModel();
if(model != null) {
try {
BeanUtils.populate(model, request.getParameterMap());
} catch (Exception e) {
throw new RuntimeException("在中央处理器中处理请求参数时发生异常", e);
}
}
}
String name = action.exeute(request, response);
......
}
处理请求参数中的null及空字符串转换问题
为了更方面的处理请求参数中的null及空字符串,转换为数值型数据的问题,加入一个监听器,在应用启动时注册转换器。
/**
* ServletContextListener接口为Servlet API中的接口,用于监听ServletContext对象的生命周期。
* 当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent事件,该事件由
* ServletContextListener来处理。
* @author Administrator
*/
@WebListener
public class BeanUtilsListener implements ServletContextListener {
/**
* 当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,
* 容器会先销毁所有的Servlet和Filter 过滤器。
*/
@Override
public void contextDestroyed(ServletContextEvent arg0) {
// TODO Auto-generated method stub
}
/**
* 当Servlet 容器启动Web应用时调用该方法。
* 在调用完该方法之后,容器再对Filter 初始化,
* 并且对那些在Web应用启动时就需要被初始化的Servlet进行初始化。
*/
@Override
public void contextInitialized(ServletContextEvent arg0) {
ConvertUtils.register(new IntegerConverter(null), Integer.class);
ConvertUtils.register(new FloatConverter(null), Float.class);
ConvertUtils.register(new DoubleConverter(null), Double.class);
ConvertUtils.register(new LongConverter(null), Long.class);
ConvertUtils.register(new BigDecimalConverter(null), BigDecimal.class);
}
}
每个Action只能有一个execute方法,如果处理一个模块的增删改查则需要单独编写多个Action,这样会比较麻烦。如果在一个Action实例中可以处理多个请求方法,则框架会更加灵活。
规定请求参数中必须包含一个“methodName”参数,用于指定处理请求的Action中的方法
构建一个抽象类,该类实现Action子控制器接口,通过反射机制调用其子类中的方法,方法名有请求参数“methodName”指定。
需要开发的Action子控制器,集成上一步构建的抽象类,编写的用于处理请求的方法名要与请求参数“methodName”指定的方法名匹配,同时需要HttpServletRequest和HttpServletResponse两个参数(保留该参数主要为了方面对请求的处理)
构建抽象类
public abstract class AbstractDispatchAction implements Action {
@Override
public String exeute(HttpServletRequest request, HttpServletResponse response) {
String methodName = request.getParameter("methodName");
Class<? extends AbstractDispatchAction> clazz = this.getClass();
try {
Method method = clazz.getDeclaredMethod(
methodName,
HttpServletRequest.class,
HttpServletResponse.class);
return (String)method.invoke(this, request,response);
} catch (Exception e) {
throw new RuntimeException("在调用Action中的["+methodName+"]方法是异常", e);
}
}
}
public class StudentAction extends AbstractDispatchAction implements ModelDrive {
private Student student = new Student();
@Override
public Object getModel() {
return student;
}
/*@Override
public String exeute(HttpServletRequest request, HttpServletResponse response) {
System.out.println("StudentAction = " + student);
return "students";
}*/
public String getStudents(HttpServletRequest request, HttpServletResponse response) {
System.out.println("getStudents");
System.out.println("StudentAction = " + student);
return "students";
}
public String addStudent(HttpServletRequest request, HttpServletResponse response) {
System.out.println("addStudent");
System.out.println("add student = " + student);
return "students";
}
}
注意:在请求中需要添加一个methodName的固定参数,该参数指定了需要调用的Action中的方法的名称。
可以将通用分页,字符编码过滤器等组件一集成到mvc框架中便于复用。
将自定义mvc框架打成jar包,以便于在其他项目中使用。
项目 --(右击)–>Export