自定义Spring MVC框架(简易版)

305 阅读4分钟

Spring MVC原理

Spring MVC原理如下图所示

1544766140377.png

  • 1.用户发送请求至前端控制器DispatcherServlet

  • 2.DispatcherServlet收到请求调用HandlerMapping处理器映射器。

  • 3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet

  • 4.DispatcherServlet通过HandlerAdapter处理器适配器调用处理器

  • 5.HandlerAdapter执行处理器(handler,也叫后端控制器)。

  • 6.Controller执行完成返回ModelAndView

  • 7.HandlerAdapterhandler执行结果ModelAndView返回给DispatcherServlet

  • 8.DispatcherServletModelAndView传给ViewReslover视图解析器

  • 9.ViewReslover解析后返回具体View对象

  • 10.DispatcherServletView进行渲染视图(即将模型数据填充至视图中)。

  • 11.DispatcherServlet响应用户

六大组件

  • DispatcherServlet:前端控制器

    用户请求到达前端控制器,它就相当于mvc模式中的C,dispatcherServlet是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet的存在降低了组件之间的耦合性。

  • HandlerMapping:处理器映射器

    HandlerMapping负责根据用户请求找到Handler即处理器,springmvc提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

  • Handler:处理器

    Handler 是继DispatcherServlet前端控制器的后端控制器,在DispatcherServlet的控制下Handler对具体的用户请求进行处理。

    由于Handler涉及到具体的用户业务请求,所以一般情况需要程序员根据业务需求开发Handler。

  • HandlAdapter:处理器适配器

    通过HandlerAdapter对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。

  • View Resolver:视图解析器

    View Resolver负责将处理结果生成View视图,View Resolver首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户。

  • View:视图

    springmvc框架提供了很多的View视图类型的支持,包括:jstlView、freemarkerView、pdfView等。我们最常用的视图就是jsp。

    一般情况下需要通过页面标签或页面模版技术将模型数据通过页面展示给用户,需要由程序员根据业务需求开发具体的页面。

说明: 在springmvc的各个组件中,处理器映射器、处理器适配器、视图解析器称为springmvc的三大组件

需要用户开发的组件有:处理器、视图

自定义

1.首先pom.xml文件引入依赖如下:

<project xmlns="http://maven.apache.org/POM/4.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>com.kkb</groupId>
   <artifactId>springmvc-custom</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <packaging>war</packaging>

   <dependencies>
      <!-- jstl 取决于视图对象是否是JSP -->
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>jstl</artifactId>
         <version>1.2</version>
      </dependency>

      <!-- servlet -->
      <dependency>
         <groupId>javax.servlet</groupId>
         <artifactId>javax.servlet-api</artifactId>
         <version>3.1.0</version>
         <scope>provided</scope>
      </dependency>

      <!-- jackson依赖 -->
      <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
         <version>2.9.6</version>
      </dependency>

      <!-- DOM4J依赖 -->
      <dependency>
         <groupId>dom4j</groupId>
         <artifactId>dom4j</artifactId>
         <version>1.6.1</version>
      </dependency>
      <!-- Xpath依赖 -->
      <dependency>
         <groupId>jaxen</groupId>
         <artifactId>jaxen</artifactId>
         <version>1.1.6</version>
      </dependency>



   </dependencies>
   <build>
      <plugins>
         <!-- 配置Maven的JDK编译级别 -->
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.2</version>
            <configuration>
               <source>1.8</source>
               <target>1.8</target>
               <encoding>UTF-8</encoding>
            </configuration>
         </plugin>

         <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
            <configuration>
               <port>8080</port>
               <path>/</path>
            </configuration>
         </plugin>
      </plugins>
   </build>
</project>

2.编写web.xml配置文件,配置DispatcherServlet前端控制器,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="3.1">
  <servlet>
    <servlet-name>DispatcherServlet</servlet-name>
    <servlet-class>com.kkb.springmvc.servlet.DispatcherServlet</servlet-class>
    <!-- 表示在Tomcat启动的时候,就创建该Servlet对象 -->
    <load-on-startup>2</load-on-startup>
    <init-param>
       <param-name>contextConfigLocation</param-name>
       <param-value>springmvc.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>DispatcherServlet</servlet-name>
    <!--不会拦截jsp,但是会拦截静态资源 -->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

3.springmvc.xml配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans>
   <!-- name属性就是URI -->
   <!-- class属性就是处理器类的全路径 -->
   <bean name="/addUser" class="com.kkb.springmvc.handler.AddUserHandler" ></bean>
   <bean name="/queryUser" class="com.kkb.springmvc.handler.QueryUserHandler" ></bean>
   
   <!--HandlerAdapter -->
   <bean class="com.kkb.springmvc.handleradapter.HttpRequestHandlerAdapter" ></bean>
</beans>

4.自定义Spring MVC框架中重要的几个组件的关系如下:

image.png

DispatcherServlet前端控制器如下:

/**
 * DispatcherServlet作用: 1:接收所有的请求URL(除了JSP请求) 2:不处理具体业务,只做调度者(请求分发)
 * 3:响应处理结果(由具体业务处理器返回给DispatcherServlet)
 * 
 * 思考:DispatcherServlet的编写怎么才能符合开闭原则 结论:可以使用委托模式,将请求查找和请求处理交给不同的类去处理 具体方案:
 * 1:专门根据请求URL查找处理器的类:处理器映射器 2:专门执行对应处理器的类:处理器适配器
 * 
 * @author huangshuai
 *
 */
public class DispatcherServlet extends HttpServlet {

   private static final long serialVersionUID = 116714888769576151L;

   private Map<String, Object> handlerMappings = new HashMap<>();
   private List<HandlerAdapter> handlerAdapters = new ArrayList<>();
   private static final String CONTEXT_CONFIG_LOCATION = "contextConfigLocation";

   @Override
   public void init(ServletConfig config) throws ServletException {
      super.init(config);
      // 获取servlet中的初始化参数,即springmvc.xml
      String contextPath = config.getInitParameter(CONTEXT_CONFIG_LOCATION);
      // 初始化spring配置文件中的信息
      initHandlerMapping(contextPath);
      initHandlerAdapter(contextPath);
   }

   /**
    * 初始化加载HandlerAdapter
    * 
    * @param contextPath
    */
   private void initHandlerAdapter(String contextPath) {
      handlerAdapters = XmlUtils.getRequestAdapterFromXML(contextPath);
   }

   /***
    * 初始化加载HandlerMapping
    * 
    * @param contextPath
    *            xml配置文件的路径
    */
   private void initHandlerMapping(String contextPath) {
      // 使用工具类读取XML配置文件,获取配置信息
      handlerMappings = XmlUtils.getRequestMappingFromXML(contextPath);
   }

   protected void doGet(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      try {
         // 设置请求体的字符编码
         request.setCharacterEncoding("UTF-8");

         // 获取请求URL(URI是URL的父类)
         String uri = request.getRequestURI();
         // 根据URL查找处理器
         Object handler = getHandler(uri);

         if (handler == null) {
            // response返回
         }
         // 根据处理器查找对应的处理器适配器
         HandlerAdapter ha = getHandlerAdapter(handler);

         if (ha == null) {
            // response返回
         }

         // 调用处理器的方法,进行业务处理:不知道处理器的具体,无法强制,无法进行方法调用
         // 此处需要使用到适配器模式(适配不同的类或者接口为统一的接口)
         // HandlerAdapter接口为了适配不同的处理器类,为统一的一个类,方便方法调用
         // 适配器类是一个处理器类型对应一个适配器类
         ModelAndView mv = ha.handle(request, response, handler);

         // 处理返回结果
         handleModelAndView(mv);
      } catch (Exception e) {
         e.printStackTrace();
      }
   }

   /**
    * 处理返回值
    * 
    * @param mv
    */
   private void handleModelAndView(ModelAndView mv) {
      // TODO Auto-generated method stub

   }

   /**
    * 根据查找到的处理器,获取对应的处理器适配器
    * 
    * @param handler
    * @return
    */
   private HandlerAdapter getHandlerAdapter(Object handler) {
      for (HandlerAdapter ha : handlerAdapters) {
         // 是否适配
         if (ha.supports(handler)) {
            return ha;
         }
      }
      return null;
   }

   /**
    * 根据请求URL查找处理器对象
    * 
    * @param uri
    * @return
    */
   private Object getHandler(String uri) {
      return handlerMappings.get(uri);
   }

   protected void doPost(HttpServletRequest request, HttpServletResponse response)
         throws ServletException, IOException {
      doGet(request, response);
   }

}

5.HandlerAdapter处理器适配器如下:

/**
 * 处理器适配器
 * 作用:适配不同类型的处理器,可以让DispatcherServlet按照统一的方式调用不同类型的处理器
 * 
 * @author huangshuai
 *
 */
public interface HandlerAdapter {

   /**
    * 是否适配该处理器
    * @param handler
    * @return
    */
   boolean supports(Object handler);
   
   /**
    * 执行适配成功的处理器
    * @param request
    * @param response
    * @param handler 适配成功的处理器
    * @return 业务处理成功的返回值
    * @throws Exception
    */
   ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

HttpRequestHandlerAdapter处理器适配器如下:

public class HttpRequestHandlerAdapter implements HandlerAdapter {

   @Override
   public boolean supports(Object handler) {
      if (handler instanceof HttpRequestHandler) {
         return true;
      }
      return false;
   }

   @Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception {
      // 这种处理器没有返回值
      ((HttpRequestHandler)handler).handleRequest(request, response);
      return null;
   }

}

SimpleControllerHandlerAdapter处理器适配器如下:

public class SimpleControllerHandlerAdapter implements HandlerAdapter {

   @Override
   public boolean supports(Object handler) {
      if (handler instanceof Controller) {
         return true;
      }
      return false;
   }

   @Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
         throws Exception { 
      ModelAndView modelAndView = ((Controller)handler).handleRequest(request, response);
      return modelAndView;
   }

}

6.handler处理器如下:

public interface Controller {
   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}
/**
 * 制定编写处理器的规则
 * @author huangshuai
 *
 */
public interface HttpRequestHandler {

   public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

简单的处理器实现类如下:

/***
 * 处理器:专门处理不同业务请求的
 * 
 * @author huangshuai
 */
public class AddUserHandler implements HttpRequestHandler{

   public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

      // 设置响应体的媒体类型
      response.setContentType("text/plain;charset=utf-8");
      // 响应
      response.getWriter().print("添加成功");
   }
}
/***
 * 处理器:专门处理不同业务请求的
 * 
 * @author huangshuai
 */
public class QueryUserHandler implements HttpRequestHandler{

   public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
      // 调用业务方法,处理业务请求
      // 设置请求体的字符编码
      request.setCharacterEncoding("UTF-8");

      // 设置响应体的媒体类型
      response.setContentType("application/json;charset=utf-8");
      // 接收参数
      String id = request.getParameter("id");

      if (id == null) {
         // 响应
         response.getWriter().print("id参数不能为空");
         return;
      }
      // 第二步:调用业务方法
      User user = new User();
      user.setId(Integer.parseInt(id));
      user.setUsername("zhangsan");
      user.setAddress("上地");

      // 响应
      response.getWriter().print(JsonUtils.object2Json(user));
   }
}
/***
 * 处理器:专门处理不同业务请求的
 * 
 * @author huangshuai
 */
public class QueryUserHandler2 implements Controller {

   public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {

      // 接收参数
      String id = request.getParameter("id");
      // 调用业务层方法处理业务,并返回处理结果
      User user = new User();
      user.setId(Integer.parseInt(id));
      user.setUsername("zhangsan");
      user.setAddress("上地");

      // 返回结果
      ModelAndView mv = new ModelAndView();
      mv.addAttribute("user", user);
      mv.setViewName("");

      return mv;
   }
}

7.ModelAndView如下:

public class ModelAndView {
   // 数据模型
   private Map<String, Object> model;

   // 视图名称
   private String viewName;

   public ModelAndView() {
      super();
      this.model = new HashMap<>();
   }

   public void addAttribute(String attributeName, Object attributeValue) {
      model.put(attributeName, attributeValue);
   }

   public String getViewName() {
      return viewName;
   }
   
   public void setViewName(String viewName) {
      this.viewName = viewName;
   }
}

8.解析xml的工具类XmlUtils如下:

public class XmlUtils {

   /**
    * 获取HandlerMapping
    * 根据bean标签的id值获取bean标签的class值(类的全路径)
    * 使用dom4j解析springmvc.xml配置文件
    */
   public static Map<String, Object> getRequestMappingFromXML(String contextPath) {
      Map<String, Object> requestMapping = new HashMap<>();
      try {
         // 创建SaxReader对象
         SAXReader reader = new SAXReader();
         // 读取XML配置文件,返回Document对象
         Document document = reader.read(XmlUtils.class.getClassLoader().getResourceAsStream(contextPath));
         // 通过xpath语句,解析XML获取指定id的bean标签的class值
         // xpath语法://bean[@id='customerDao']
         List<Element> selectNodes = document.selectNodes("//bean[@name]");

         if (selectNodes == null || selectNodes.isEmpty()) {
            return null;
         }

         for (Element element : selectNodes) {
            // 获取请求URI
            String url = element.attributeValue("name");
            // 获取对应的处理器类全路径
            String clazzName = element.attributeValue("class");
            if (url != null && url.startsWith("/") && clazzName != null && !clazzName.equals("")) {
               // 通过反射创建处理器对象
               Class<?> clazz = Class.forName(clazzName);
               Object instance = clazz.newInstance();
               //将URI和处理器对象的映射关系存入集合中
               requestMapping.put(url, instance);
            }
         }

      } catch (Exception e) {
         e.printStackTrace();
      }
      return requestMapping;
   }
   
   /**
    * 获取HandlerAdapter
    */
   public static List<HandlerAdapter> getRequestAdapterFromXML(String contextPath) {
      List<HandlerAdapter> requestAdapter = new ArrayList<>();
      try {
         // 创建SaxReader对象
         SAXReader reader = new SAXReader();
         // 读取XML配置文件,返回Document对象
         Document document = reader.read(XmlUtils.class.getClassLoader().getResourceAsStream(contextPath));
         // xpath语法://bean[@id='customerDao']
         List<Element> selectNodes = document.selectNodes("//bean");
         
         if (selectNodes == null || selectNodes.isEmpty()) {
            return null;
         }
         
         for (Element element : selectNodes) {
            // 获取对应的处理器类全路径
            String clazzName = element.attributeValue("class");
            if (clazzName != null && !clazzName.equals("")) {
               // 通过反射创建处理器对象
               Class<?> clazz = Class.forName(clazzName);
               Object instance = clazz.newInstance();
               //将URI和处理器对象的映射关系存入集合中
               if (instance instanceof HandlerAdapter) {
                  requestAdapter.add((HandlerAdapter)instance);
               }
            }
         }
         
      } catch (Exception e) {
         e.printStackTrace();
      }
      return requestAdapter;
   }
}

json工具JsonUtils类如下:

/**
 * JSON工具类(使用的是jackson实现的)
 * @author huangshuai
 *
 */
public class JsonUtils {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 将对象转换成json字符串。
     * @param data
     * @return
     */
    public static String object2Json(Object data) {
       try {
         String string = MAPPER.writeValueAsString(data);
         return string;
      } catch (JsonProcessingException e) {
         e.printStackTrace();
      }
       return null;
    }
    
    /**
     * 将json结果集转化为对象
     * 
     * @param jsonData json数据
     * @param clazz 对象中的object类型
     * @return
     */
    public static <T> T json2Pojo(String jsonData, Class<T> beanType) {
        try {
            T t = MAPPER.readValue(jsonData, beanType);
            return t;
        } catch (Exception e) {
           e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 将json数据转换成pojo对象list
     * @param jsonData
     * @param beanType
     * @return
     */
    public static <T>List<T> json2List(String jsonData, Class<T> beanType) {
       JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
       try {
          List<T> list = MAPPER.readValue(jsonData, javaType);
          return list;
      } catch (Exception e) {
         e.printStackTrace();
      }
       
       return null;
    }
    
}

9.启动项目测试

image.png

访问:http://localhost:8080/addUser

image.png

儿童版Spring MVC框架完成!!!