springMVC学习心得及手写springMVC简单实现
Spring 是一个企业级开发框架,为解决企业级项目开发过于复杂而创建的,框架的主要优势之一就是分层架构,允许开发者自主选择组件。
Spring 的两大核心机制是 IoC(控制反转)和 AOP(面向切面编程),从开发的角度讲,我们使用 Spring 框架就是用它的 IoC 和 AOP。
什么是AOP和IOC
IoC 是典型的工厂模式,通过工厂去注入对象
AOP 是代理模式的体现
- IOC也叫控制反转 ,控制反转是什么意思呢?反转的的谁? 在传统开发中一半是有调用者创建对象实例,也就是是调用者主动new出来的,那么IOC就是将这个创建对象实例的任务从调用者的手里反转到spring的IOC容器来执行,控制反转主要是反转的这个过程;
举个例子:
传统模式:以前去买菜需要自己带篮子,去超市买完自己带回家;
IOC模式:空手去超市就可以了,篮子超市已经给你准备好了,直接拿来用就好了. - 依赖注入(Dependency Injection,DI) 能实现在程序运行中,动态地向某个对象提供它所依赖的其它对象的功能。
SpringMVC的常用组件及功能 ;
- DispatcherServlet: 前端控制器,是整个流程控制的核心,控制其他组件的执行,统一调度,降低组件之间的耦合性,相当于总指挥。
- Handler:处理器,完成具体业务逻辑,相当于 Servlet 或 Action。
- HandlerMapping: DispatcherServlet 接收到请求之后,通过 HandlerMapping 将不同的请求分发到不同的 Handler。
- HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要做一些拦截处理,可以来实现这个接口。
- HandlerExecutionChain:处理器执行链,包括两部分内容,Handler 和 HandlerInterceptor(系统会有一个默认的 HandlerInterceptor,如果需要额外拦截处理,可以添加拦截器设置)。
- HandlerAdapter:处理器适配器,Handler 执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到 JavaBean 等,这一系列的操作,都是由 HandlerAdapter 来完成,- DispatcherServlet 通过 HandlerAdapter 执行不同的 Handler。
- ModelAndView:装载了模型数据和视图信息,作为 Handler 的处理结果,返回给 DispatcherServlet。
- ViewResolver:视图解析器,DispatcherServlet 通过它将逻辑视图解析成物理视图,最终将渲染结果响应给客户端。
Spring MVC 的实现流程
-
客户端请求被 DispatcherServlet(前端控制器)接收
-
根据 HandlerMapping 映射到 Handler
生成 Handler 和 HandlerInterceptor(如果有则生成) -
Handler 和 HandlerInterceptor 以 HandlerExecutionChain 的形式一并返回给 DispatcherServlet
-
DispatcherServlet 通过 HandlerAdapter 调用 Handler 的方法做业务逻辑处理
返回一个 ModelAndView 对象给 DispatcherServlet -
DispatcherServlet 将获取的 ModelAndView 对象传给 ViewResolver 视图解析器,将逻辑视图解析成物理视图 View
-
ViewResolver 返回一个 View 给 DispatcherServlet
-
DispatcherServlet 根据 View 进行视图渲染(将模型数据填充到视图中)
-
DispatcherServlet 将渲染后的视图响应给客户端
-
看到上面的实现原理,可能会有这样的担心,Spring MVC 如此众多的组件开发起来一定很麻烦吧?答案是否定的,Spring MVC 使用起来非常简单,很多组件都由框架提供,作为开发者我们直接使用即可,并不需要自己手动编写代码,真正需要开发者进行编写的组件只有两个: -
Handler,处理业务逻辑
-
View,JSP 做展示
Spring MVC 底层实现
一个 Spring MVC 框架,相比于 MyBatis 框架,Spring MVC 要简单一些,只需要 XML 解析 + 反射就可以完成,不需要 JDK 动态代理。 强调文本
下面,来手写一下springMVC的简单实现,废话不多说,直接上干货
要自己写框架,必须理解框架的底层原理和运行机制,这部分在上一部分已经讲过
1.MyDispatcherServlet
首先需要一个前置控制器 DispatcherServlet,作为整个流程的核心,由它去调用其他组
件,共同完成业务。主要组件有两个:一是 Controller,调用其业务方法 Method,执行业务逻辑;
二是 ViewResolver 视图解析器,将业务方法的返回值解析为物理
视图 + 模型数据,返回客户端。
我们自己写框架就按照这个思路来。
初始化工作
-
根据 Spring IoC 的思路,需要将参与业务的对象全部创建并保存,供流程调用。因此,首先需要创建 Controller 对象,HTTP 请求是通过注解找到对应的 Controller 对象,我们需要将所有的 Controller 与其注解建立关联,很显然,使用 key-value 结构的 Map 集合来保存最合适不过了,这样就模拟了 IoC 容器。
-
Controller 的 Method 也是通过注解与 HTTP 请求映射的。同样地,我们需要将所有的 Method 与其注解建立关联,HTTP 直接通过注解的值找到对应的 Method,这里也用 Map 集合保存。
-
实例化视图解析器
初始化工作完成,接下来处理 HTTP 请求,业务流程如下:
- DispatcherServlet 接收请求,通过映射从 IoC 容器中获取对应的 Controller 对象;
- 根据映射获取 Controller 对象对应的 Method;
- 调用 Method,获取返回值;
- 将返回值传给视图解析器,返回物理视图;
- 完成页面跳转。
创建类
思路捋清楚了,接下来开始写代码,我们需要创建以下类。
- MyDispatcherServlet:模拟 DispatcherServlet
- MyController:模拟 Controller 注解
- MyRequestMapping:模拟 RequestMapping 注解
- MyViewResolver:模拟 ViewResolver 视图解析器
- 创建 MyDispatcherServlet,init 方法完成初始化。
把 Controller 与注解进行关联
将 Controller 与注解进行关联,保存到 iocContainer 中。哪些 Controller 是需要添加到 iocContainer 中的呢?必须同时满足两点: -
- springmvc.xml 中配置扫描的类
-
- 类定义处添加了注解
注意这两点必须同时满足。
- 类定义处添加了注解
(1)创建 MyController 注解,作用目标为类:
package morin.springmvc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* controller注解 作用目标为类
* 作用范围:运行时
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyController {
String value() default "";
}
(2)创建 MyRequestMapping 注解,作用目标为类和方法:
package morin.springmvc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义requestMapping注解,作用目标为类和方法,作用范围为运行时
*/
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyRequestMapping {
String value() default "";
}
(3)创建 MyDispatcherServlet,核心控制器,init 完成初始化工作,doPost 处理 HTTP 请求:
package morin.springmvc.servlet;
import morin.springmvc.annotation.MyController;
import morin.springmvc.annotation.MyRequestMapping;
import morin.springmvc.view.MyViewResolver;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
/**
* MyDispathcerServlet class
*
* @author Molrin
* @date 2018/11/5 0005
*/
public class MyDispathcerServlet extends HttpServlet {
/**
* 模拟IDC容器,保存bean对象
*/
private Map<String, Object> iocContainer = new HashMap<String, Object>();
/**
* 保存handler映射
*/
private Map<String, Method> methods = new HashMap<String, Method>();
/**
* 视图解析器
*/
private MyViewResolver myViewResolver;
@Override
public void init(ServletConfig config) throws ServletException {
//扫描Controller,创建实例对象,并存入iocContainer
scanController(config);
//初始化Handler映射
initHandlerMapping();
//加载视图解析器
loadViewResolver(config);
}
/**
* 加载视图解析器
* @param config 配置
*/
private void loadViewResolver(ServletConfig config) {
//创建SAXReader解析xml配置
SAXReader saxReader = new SAXReader();
try {
//解析springMVC.xml
String path = config.getServletContext().getRealPath("") + "\\WEB-INF\\classes\\" + config.getInitParameter("contextConfigLocation");
Document document = saxReader.read(path);
Element root = document.getRootElement();
Iterator iterator = root.elementIterator();
while (iterator.hasNext()) {
Element element = (Element) iterator.next();
if ("bean".equals(element.getName())) {
String className = element.attributeValue("class");
Class<?> aClass = Class.forName(className);
Object o = aClass.newInstance();
//获取方法对象
Method setPrefix = aClass.getMethod("setPrefix", String.class);
Method setSuffix = aClass.getMethod("setSuffix", String.class);
Iterator beanIter = element.elementIterator();
//获取property值
HashMap<String, String> map = new HashMap<String, String>();
while (beanIter.hasNext()) {
Element next = (Element) beanIter.next();
String name = next.attributeValue("name");
String value = next.attributeValue("value");
map.put(name, value);
}
for (String key : map.keySet()) {
//反射机制调用set方法,完成赋值
if ("prefix".equals(key)) {
setPrefix.invoke(o, map.get(key));
}
if ("suffix".equals(key)) {
setSuffix.invoke(o, map.get(key));
}
}
myViewResolver = (MyViewResolver)o;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 初始化handler映射
*/
private void initHandlerMapping() {
Set<String> keySet = iocContainer.keySet();
for (String value : keySet) {
//获取每个类的class对象
Class<?> aClass = iocContainer.get(value).getClass();
//获取该类的方法数组
Method[] methods = aClass.getMethods();
for (Method method : methods) {
//判断方法是否带有MyRequestMapping注解
if (method.isAnnotationPresent(MyRequestMapping.class)) {
//获取该注解中的值
MyRequestMapping annotation = method.getAnnotation(MyRequestMapping.class);
String ann = annotation.value().substring(1);
//将带有注解的方法存入处理器映射器
this.methods.put(ann, method);
}
}
}
}
/**
* 扫描controller
*
* @param config 配置
*/
private void scanController(ServletConfig config) {
//创建解析xml对象
SAXReader saxReader = new SAXReader();
try {
//获取xml路径
String path = config.getServletContext().getRealPath("") + "\\WEB-INF\\classes\\" + config.getInitParameter("contextConfigLocation");
//获取document
Document document = saxReader.read(path);
//获取根元素
Element rootElement = document.getRootElement();
//获取元素迭代器
Iterator iterator = rootElement.elementIterator();
//循环遍历
while (iterator.hasNext()) {
//获取单个元素
Element element = (Element) iterator.next();
//判断是否有包扫描标签
if ("component-scan".equals(element.getName())) {
//获取扫描的包名
String basePackage = element.attributeValue("base-package");
//获取basePackage包下的所有类名
List<String> classNames = getClassName(basePackage);
for (String className : classNames) {
//根据全限定名获取class对象
Class<?> aClass = Class.forName(className);
//判断是否有@Controller注解
if (aClass.isAnnotationPresent(MyController.class)) {
MyRequestMapping requestMapping = aClass.getAnnotation(MyRequestMapping.class);
//获取该注解中的value
String value = requestMapping.value().substring(1);
//使用instance方法创建Controller对象并放入iocContainer
iocContainer.put(value, aClass.newInstance());
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private List<String> getClassName(String basePackage) {
//创建返回值
List<String> classNames = new ArrayList<String>();
//将全限定名中的. 全部替换为/
String newNames = basePackage.replace(".", "/");
//获取当前线程的类加载器
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
URL url = contextClassLoader.getResource(newNames);
if (url != null) {
File file = new File(url.getPath());
//获取该目录下的所有文件路径的File对象集合
getFileName(file.listFiles(), basePackage,classNames);
}
return classNames;
}
private void getFileName(File[] file,String basePackage,List<String> classNames){
String path;
if (file != null) {
for (File file1 : file) {
if (!file1.isFile()) {
File[] files = file1.listFiles();
path = basePackage+"." +file1.getName();
getFileName(files,path,classNames);
}else {
path = basePackage + "." + file1.getName().replace(".class", "");
classNames.add(path);
}
}
}
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//获取请求
String handlerUri = req.getRequestURI().split("/")[1];
//获取controller实例
Object obj = iocContainer.get(handlerUri);
String methodUri = req.getRequestURI().split("/")[2];
//获取method实例
Method method = methods.get(methodUri);
try {
//反射机制调用方法
String value = (String) method.invoke(obj,req.getParameter("sessionId"));
//视图解析器将逻辑视图转换成物理视图
String view = myViewResolver.jspMapping(value);
//页面跳转
req.getRequestDispatcher(view).forward(req, resp);
}catch (Exception e){
e.printStackTrace();
}
}
}
(4)创建视图解析器 MyViewResolver:
package morin.springmvc.view;
/**
* MyViewResolver class
* 视图解析器
* @author Molrin
* @date 2018/11/5 0005
*/
public class MyViewResolver {
/**
* 视图前缀
*/
private String prefix;
/**
* 视图后缀
*/
private String suffix;
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public String jspMapping(String value) {
return prefix+value+suffix;
}
}
(5)创建 TestController,处理业务请求:
package morin.springmvc.controller;
import morin.springmvc.annotation.MyController;
import morin.springmvc.annotation.MyRequestMapping;
import org.springframework.web.bind.annotation.CookieValue;
/**
* TestController class
*
* @author Molrin
* @date 2018/11/5 0005
*/
@MyController
@MyRequestMapping("/testController")
public class TestController {
@MyRequestMapping(value = "/test")
public String testHello(){
System.out.println("执行controller");
System.out.println("success!");
return "index";
}
}
(6)springMVC.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--配置包扫描-->
<context:component-scan base-package="morin.springmvc"/>
<!--配置自定义视图解析器-->
<bean class="morin.springmvc.view.MyViewResolver">
<property name="prefix" value="/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>
(7)web.xml:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<!--配置自定义的DispatcherServlet核心控制器-->
<servlet>
<servlet-name>mySpringMVC</servlet-name>
<servlet-class>morin.springmvc.servlet.MyDispathcerServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>springMVC.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>mySpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
(8)测试: