什么是MVC
一种软件架构思想,在设计软件的时候将软件分为模型,视图,控制器三部分
- 模型负责数据处理(即业务逻辑 Model)
- 视图负责数据展现(即表示逻辑 View)
- 控制器负责协调模型和视图
视图将请求发送给控制器,由控制器来选择对应的模型来处理,模型将返回的结果也给控制器,由控制器选择对应的视图来展现
如何使用MVC
开发一个web应用时,我们可以使用servlet充当控制器, 使用jsp充当视图,使用java类充当模型。
MVC的优缺点
优点:
- 将数据处理和数据展现分离,方便代码维护
- 方便工作和协调测试
缺点:
- 增加代码量
- 增加软件设计的难度,开发成本
有一定规模、并且要求具有良好的维护性与扩展性的软件,才需要使用MVC。
实现MVC
目标
实现一个通用的控制器 DispatcherServlet
架构
主要组件
- DispatcherServlet (控制器)
- HandlerMapping (映射处理器)
- Controller (处理器)
其中
DispatcherServlet负责接收请求,依据HandlerMapping的配置,调用处理器来处理。
HandlerMapping负责提供请求路径与处理器的对应关系
Controller负责处理业务逻辑返回具体的视图名
实现
1.创建一个注解RequestMapping:用于返回方法的注解值
package mvc;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface RequestMapping {
//返回该方法的注解值
String value();
}
2.创建Controller:子控制器,用于封装具体的处理逻辑,为了代码简洁,这里就把具体的业务逻辑用一句打印的话省略。控制器里面的每个方法上面都带上我们自己写的注解。同时执行完业务逻辑后就返回要展现给用户看的视图名称即jsp的页面名称。
package mvc;
import javax.servlet.http.HttpServletRequest;
public class Controller {
@RequestMapping("login.do")
public String login(HttpServletRequest request) {
System.out.println("开始执行具体的登入操作");
return "login";
}
@RequestMapping("addUser.do")
public String addUser(HttpServletRequest request) {
System.out.println("开始执行添加用户");
return "addUser";
}
@RequestMapping("reg.do")
public String reg(HttpServletRequest request) {
System.out.println("开始执行注册用户");
return "reg";
}
}
3.创建处理者Handler,属性有controller(子控制器对象) 和 method(字控制器的一个方法)
建立一个Method和它对应的controller对象的映射,提供一个可动态执行该Method方法.
package mvc;
import java.lang.reflect.Method;
import javax.servlet.http.HttpServletRequest;
/**
* 处理者、处理器 建立一个Method和它对应的 Controller对象的映射 提供一个可以动态执行该Method的方法
*/
public class Handler {
Object controller; // 子控制器对象
Method method; // 子控制器的一个方法
public Handler() {
}
public Handler(Object controller, Method method) {
this.controller = controller;
this.method = method;
}
@Override
public String toString() {
return "Handler [controller=" + controller + ", method=" + method + "]";
}
public String execute(HttpServletRequest req) throws Exception {
// 反射调用controller中的method方法
return (String) method.invoke(controller, req);
}
}
4.创建HandlerMapping:用来管理Map集合的类
- Map集合管理了url与Handler的映射关系
- 提供Map的初始化方法
- 提供使用url从Map集合中查询Handler的方法
package mvc;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class HandlerMapping {
@Override
public String toString() {
return "HandlerMapping [mapping=" + mapping + "]";
}
Map<String,Handler> mapping=new HashMap<String, Handler>();
/**
* 遍历该类中的所有方法和方法的注解并将注解值和方法的对应关系保存到Map中
* @param className Controller的包名.类名
* @throws Exception
*/
public void parseController(String className)throws Exception {
Class cz=Class.forName(className);
//利用反射创建controller对象
Object controller=cz.newInstance();
Method[] methods=cz.getDeclaredMethods();
for (Method method : methods) {
//获得所有RequestMapping类型的注解
RequestMapping an=method.getAnnotation(RequestMapping.class);
if(an!=null) {
String url=an.value();
//创建Handler对象
Handler handler=new Handler(controller,method);
mapping.put(url,handler);
}
}
}
/**
* 根据url查找对应Handler的方法
* @param url (用户请求的url)
* @return
*/
public Handler getHandler(String url) {
return mapping.get(url);
}
}
到这里,我们就完成了一半的工作的啦,写个测试类测试一下,看是否能够通过反射与注解成功的调用方法,并且返回我们需要调用的视图
package mvc;
import javax.servlet.http.HttpServletRequest;
public class test {
public static void main(String[] args) throws Exception {
HttpServletRequest request=null;
// 测试HandlerMapping
HandlerMapping hm = new HandlerMapping();
// 解析controller
hm.parseController("mvc.Controller");
System.out.println(hm);
// 测试查找方法
Handler handler = hm.getHandler("/addUser.do");
System.out.println(handler);
// 测试反射调用方法
Object result = handler.execute(request);
System.out.println("result=" + result);
}
}
测试结果
5.开始构建负责协调模型和视图的控制器DispatcherServlet(前端控制器)
将复杂的web处理代码封装在前端控制器内部
5.1将所有的controller配置到mvc.xml文件中,这里只添加一个,后面如果有其他的控制器可以按照一样的格式进行配置
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean class="mvc.Controller" ></bean>
</beans>
5.2将要加载的xml文件信息配置到web.xml中,以及设置服务器启动就加载和实例化该Servlet
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>mvc.DispatcherServlet</servlet-class>
<init-param>
<param-name>fileName</param-name>
<param-value>mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
5.3准备一个注册界面,登入界面,添加用户界面 放在WEB-INF下面的jsp文件夹下面
里面内容就比较急随意了,这里测试的话就是直接给了句话
5.4构建DispatcherServlet
package mvc;
import java.io.IOException;
import java.io.InputStream;
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;
/**
* 前端控制器,接收用户对所有的Servlet的请求
*
* @author admin
*
*/
public class DispatcherServlet extends HttpServlet {
HandlerMapping handlerMapping = new HandlerMapping();
@Override
public void init() throws ServletException {
// 初始化HandlerMapping
try {
// 使用dom4j解析mvc.xml的内容
SAXReader reader = new SAXReader();
// 读取web.xml中配置的配置文件名称
String fileName = getInitParameter("fileName");
// 获取配置文件的输入流
InputStream is =getClass().getClassLoader().getResourceAsStream(fileName);
Document document = reader.read(is);
// 获取根节点
Element rootEle = document.getRootElement();
// 获取所有标签名为 bean 的元素
List<Element> list = rootEle.elements("bean");
if (list != null) {
for (Element ele : list) {
// 获取bean标签的 class 属性的值
String className = ele.attributeValue("class");
// 解析className对应的子控制器
handlerMapping.parseController(className);
}
}
// 查看hanlderMapping解析的结果
System.out.println("hm=" + handlerMapping);
} catch (Exception e) {
// 异常处理原则:能处理尽量处理,处理不了向上抛
e.printStackTrace();
throw new ServletException("控制器解析异常", e);
}
}
protected void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
try {
// 从web.xml中读取配置的初始化参数
String encode = getServletContext().getInitParameter("encode");
response.setContentType("text/html;charset=" + encode);
request.setCharacterEncoding(encode);
// 获得用户请求的url
String url = request.getRequestURI();
url = url.substring(url.lastIndexOf("/"));
System.out.println("url" + url);
// 从HandlerMapping中获取url对应的Handler
Handler handler = handlerMapping.getHandler(url);
System.out.println("handler" + handler);
// 反射调用子控制器中对应的方法
String path = handler.execute(request);
System.out.println(path + "path");
// 拼接jsp的路径
path = "/WEB-INF/jsp/" + path + ".jsp";
// 基于方法返回值实现请求转发
request.getRequestDispatcher(path).forward(request, response);
} catch (Exception e) {
e.printStackTrace();
response.getWriter().write("服务器故障:" + e.getMessage());
}
}
}
启动服务,开始测试
总结
1.DispatcherServlet的职责
- 接收所有的用户请求
- 封装与web相关的代码(如:乱码,异常,重定向、转发…)
- 初始化HandlerMapping,查询url对应的handler
- 调用handler的execute方法,反射调用子控制器
2.HandlerMapping的职责
- 管理Map集合(即url和handler的映射以及handler(controller,method)的映射)44
- 提供parseController方法,反射遍历所有的注解和方法,建立对应的映射关系
- 提供基于url查找对应handler的方法
3.Handler
- 维护controller与method的对应关系
- 提供execute() 提供反射调用method
4.Controller(子控制器)
- 封装具体的业务逻辑
- 提供@RequestMapping注解,建立method与url的映射关系