Spring 是一个企业级开发框架,为解决企业级项目开发过于复杂而创建的,框架的主要优势之一就是分层架构,允许开发者自主选择组件。
Spring 的两大核心机制是 IoC(控制反转)和 AOP(面向切面编程),从开发的角度讲,我们使用 Spring 框架就是用它的 IoC 和 AOP。
IoC 是典型的工厂模式,通过工厂去注入对象
AOP 是代理模式的体现
客户端请求被 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 框架,相比于 MyBatis 框架,Spring MVC 要简单一些,只需要 XML 解析 + 反射就可以完成,不需要 JDK 动态代理。 强调文本
下面,来手写一下springMVC的简单实现,废话不多说,直接上干货
要自己写框架,必须理解框架的底层原理和运行机制,这部分在上一部分已经讲过
首先需要一个前置控制器 DispatcherServlet,作为整个流程的核心,由它去调用其他组
件,共同完成业务。主要组件有两个:一是 Controller,调用其业务方法 Method,执行业务逻辑;
二是 ViewResolver 视图解析器,将业务方法的返回值解析为物理
视图 + 模型数据,返回客户端。
我们自己写框架就按照这个思路来。
初始化工作
根据 Spring IoC 的思路,需要将参与业务的对象全部创建并保存,供流程调用。因此,首先需要创建 Controller 对象,HTTP 请求是通过注解找到对应的 Controller 对象,我们需要将所有的 Controller 与其注解建立关联,很显然,使用 key-value 结构的 Map 集合来保存最合适不过了,这样就模拟了 IoC 容器。
Controller 的 Method 也是通过注解与 HTTP 请求映射的。同样地,我们需要将所有的 Method 与其注解建立关联,HTTP 直接通过注解的值找到对应的 Method,这里也用 Map 集合保存。
实例化视图解析器
初始化工作完成,接下来处理 HTTP 请求,业务流程如下:
创建类
思路捋清楚了,接下来开始写代码,我们需要创建以下类。
(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:
<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:
<web-app>
<servlet>
<servlet-name>mySpringMVCservlet-name>
<servlet-class>morin.springmvc.servlet.MyDispathcerServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>springMVC.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>mySpringMVCservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
web-app>
点击此处获取源码