MVC
MVC 全称:Model 模型、 View 视图、 Controller 控制器。
MVC 最早出现在 JavaEE 三层中的 Web 层,它可以有效的指导 Web 层的代码如何有效分离,单独工作。
- View 视图:只负责数据和界面的显示,不接受任何与显示数据无关的代码,便于程序员和美工的分工合作——JSP/HTML。
- Controller 控制器:只负责接收请求,调用业务层的代码处理请求,然后派发页面,是一个“调度者”的角色——Servlet。转到某个页面。或者是重定向到某个页面。
- Model 模型:将与业务逻辑相关的数据封装为具体的 JavaBean 类,其中不掺杂任何与数据处理相关的代码——JavaBean/domain/entity/pojo。
MVC是一种思想
MVC 的理念是将软件代码拆分成为组件,单独开发,组合使用,目的还是为了降低耦合度,让代码合理分层。方便后期升级和维护。
-
最初的做法是: 一个请求对应一个Servlet,这样存在的问题是servlet太多了
-
把一些列的请求都对应一个Servlet, IndexServlet/AddServlet/EditServlet/DelServlet/UpdateServlet -> 合并成xxxServlet,通过一个operate的值来决定调用FruitServlet中的哪一个方法,使用的是switch-case。
-
在上一个版本中,Servlet中充斥着大量的switch-case,试想一下,随着我们的项目的业务规模扩大,那么会有很多的Servlet,也就意味着会有很多的switch-case,这是一种代码冗余。因此,我们在servlet中使用了反射技术,我们规定operate的值和方法名一致,那么接收到operate的值是什么就表明我们需要调用对应的方法进行响应,如果找不到对应的方法,则抛异常。
-
在上一个版本中我们使用了反射技术,但是其实还是存在一定的问题:每一个servlet中都有类似的反射技术的代码。因此继续抽取,设计了中央控制器类:DispatcherServlet
DispatcherServlet这个类的工作分为两大部分:
1.根据url定位到能够处理这个请求的controller组件:
1)从url中提取servletPath : /fruit.do -> fruit
2)根据fruit找到对应的组件:FruitController , 这个对应的依据我们存储在applicationContext.xml中
<bean id="xxx" class="com.xxx.xxx.controllers.xxxController/>
通过DOM技术我们去解析XML文件,在中央控制器中形成一个beanMap容器,用来存放所有的Controller 组件
3)根据获取到的operate的值定位到我们FruitController中需要调用的方法
2.调用Controller组件中的方法:- 获取参数
获取即将要调用的方法的参数签名信息: Parameter[] parameters = method.getParameters();
通过parameter.getName()获取参数的名称;
准备了Object[] parameterValues 这个数组用来存放对应参数的参数值
另外,我们需要考虑参数的类型问题,需要做类型转化的工作。通过parameter.getType()获取参数的类型 - 执行方法
Object returnObj = method.invoke(controllerBean , parameterValues); - 视图处理
String returnStr = (String)returnObj;
if(returnStr.startWith(“redirect:”)){
…
}else if…
- 获取参数
使用了thymeleaf,dispatcherservlet 继承viewbaseservlet。
ViewBaseServlet:
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.WebContext;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ViewBaseServlet extends HttpServlet {
private TemplateEngine templateEngine;
@Override
public void init() throws ServletException {
// 1.获取ServletContext对象
ServletContext servletContext = this.getServletContext();
// 2.创建Thymeleaf解析器对象
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);
// 3.给解析器对象设置参数
// ①HTML是默认模式,明确设置是为了代码更容易理解
templateResolver.setTemplateMode(TemplateMode.HTML);
// ②设置前缀
String viewPrefix = servletContext.getInitParameter("view-prefix");
templateResolver.setPrefix(viewPrefix);
// ③设置后缀
String viewSuffix = servletContext.getInitParameter("view-suffix");
templateResolver.setSuffix(viewSuffix);
// ④设置缓存过期时间(毫秒)
templateResolver.setCacheTTLMs(60000L);
// ⑤设置是否缓存
templateResolver.setCacheable(true);
// ⑥设置服务器端编码方式
templateResolver.setCharacterEncoding("utf-8");
// 4.创建模板引擎对象
templateEngine = new TemplateEngine();
// 5.给模板引擎对象设置模板解析器
templateEngine.setTemplateResolver(templateResolver);
}
protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
// 1.设置响应体内容类型和字符集
resp.setContentType("text/html;charset=UTF-8");
// 2.创建WebContext对象
WebContext webContext = new WebContext(req, resp, getServletContext());
// 3.处理模板数据
templateEngine.process(templateName, webContext, resp.getWriter());
}
}
DispatcherServlet:
package com.qqzone.myssm.myspringmvc;
import com.qqzone.myssm.ioc.BeanFactory;
import com.qqzone.myssm.util.StringUtil;
import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@WebServlet("*.do")
public class DispatcherServlet extends ViewBaseServlet{
private BeanFactory beanFactory ;
public DispatcherServlet(){
}
public void init() throws ServletException {
super.init();
//之前是在此处主动创建IOC容器的
//现在优化为从application作用域去获取
//beanFactory = new ClassPathXmlApplicationContext();
ServletContext application = getServletContext();
Object beanFactoryObj = application.getAttribute("beanFactory");
if(beanFactoryObj!=null){
beanFactory = (BeanFactory)beanFactoryObj ;
}else{
throw new RuntimeException("IOC容器获取失败!");
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置编码
//request.setCharacterEncoding("UTF-8");
//假设url是: http://localhost:8080/pro15/hello.do
//那么servletPath是: /hello.do
// 我的思路是:
// 第1步: /hello.do -> hello 或者 /fruit.do -> fruit
// 第2步: hello -> HelloController 或者 fruit -> FruitController
String servletPath = request.getServletPath();
servletPath = servletPath.substring(1);
int lastDotIndex = servletPath.lastIndexOf(".do") ;
servletPath = servletPath.substring(0,lastDotIndex);
Object controllerBeanObj = beanFactory.getBean(servletPath);
String operate = request.getParameter("operate");
if(StringUtil.isEmpty(operate)){
operate = "index" ;
}
try {
Method[] methods = controllerBeanObj.getClass().getDeclaredMethods();
for(Method method : methods){
String methodName = method.getName();
if(operate.equals(methodName)){
//1.统一获取请求参数
//1-1.获取当前方法的参数,返回参数数组
Parameter[] parameters = method.getParameters();
//1-2.parameterValues 用来承载参数的值
Object[] parameterValues = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
Parameter parameter = parameters[i];
String parameterName = parameter.getName();
//如果参数名是request,response,session 那么就不是通过请求中获取参数的方式了
if("request".equals(parameterName)){
parameterValues[i] = request ;
}else if("response".equals(parameterName)){
parameterValues[i] = response ;
}else if("session".equals(parameterName)){
parameterValues[i] = request.getSession() ;
}else{
//从请求中获取参数值
String parameterValue = request.getParameter(parameterName);
String typeName = parameter.getType().getName();
Object parameterObj = parameterValue ;
if(parameterObj!=null) {
if ("java.lang.Integer".equals(typeName)) {
parameterObj = Integer.parseInt(parameterValue);
}
}
parameterValues[i] = parameterObj ;
}
}
//2.controller组件中的方法调用
method.setAccessible(true);
Object returnObj = method.invoke(controllerBeanObj,parameterValues);
//3.视图处理
String methodReturnStr = (String)returnObj ;
if(methodReturnStr.startsWith("redirect:")){ //比如: redirect:fruit.do
String redirectStr = methodReturnStr.substring("redirect:".length());
response.sendRedirect(redirectStr);
}else{
super.processTemplate(methodReturnStr,request,response); // 比如: "edit"
}
}
}
/*
}else{
throw new RuntimeException("operate值非法!");
}
*/
} catch (Exception e) {
e.printStackTrace();
throw new DispatcherServletException("DispatcherServlet出错了...");
}
}
}
DispatcherServlet中步骤大致分为:
- 从application作用域获取IOC容器
- 解析servletPath , 在IOC容器中寻找对应的Controller组件
- 准备operate指定的方法所要求的参数
- 调用operate指定的方法
- 接收到执行operate指定的方法的返回值,对返回值进行处理 - 视图处理
为什么DispatcherServlet能够从application作用域获取到IOC容器?
ContextLoaderListener在容器启动时会执行初始化任务,而它的操作就是:
- 解析IOC的配置文件,创建一个一个的组件,并完成组件之间依赖关系的注入
- 将IOC容器保存到application作用域