1.什么是MVC
MVC是三个单词的首字母缩写,它们是Model(模型)、View(视图)和Controller(控制)。
1.1.Model(模型)
模型(Model):就是业务流程/状态的处理以及业务规则的制定。业务流程的处理过程对其它层来说是黑箱操作,模型接受视图请求的数据,并返回最终的处理结果。业务模型的设计可以说是MVC最主要的核心。目前流行的EJB模型就是一个典型的应用例子,它从应用技术实现的角度对模型做了进一步的划分,以便充分利用现有的组件,但它不能作为应用设计模型的框架。它仅仅告诉你按这种模型设计就可以利用某些技术组件,从而减少了技术上的困难。对一个开发者来说,就可以专注于业务模型的设计。MVC设计模式告诉我们,把应用的模型按一定的规则抽取出来,抽取的层次很重要,这也是判断开发人员是否优秀的设计依据。抽象与具体不能隔得太远,也不能太近。MVC并没有提供模型的设计方法,而只告诉你应该组织管理这些模型,以便于模型的重构和提高重用性。我们可以用对象编程来做比喻,MVC定义了一个顶级类,告诉它的子类你只能做这些,但没法限制你能做这些。这点对编程的开发人员非常重要。
1.2.View(视图)
视图(View):代表用户交互界面,对于Web应用来说,可以概括为HTML界面,但有可能为XHTML、XML和Applet。随着应用的复杂性和规模性,界面的处理也变得具有挑战性。一个应用可能有很多不同的视图,MVC设计模式对于视图的处理仅限于视图上数据的采集和处理,以及用户的请求,而不包括在视图上的业务流程的处理。业务流程的处理交予模型(Model)处理。比如一个订单的视图只接受来自模型的数据并显示给用户,以及将用户界面的输入数据和请求传递给控制和模型。
1.3.Controller(控制)
控制(Controller):可以理解为从用户接收请求, 将模型与视图匹配在一起,共同完成用户的请求。划分控制层的作用也很明显,它清楚地告诉你,它就是一个分发器,选择什么样的模型,选择什么样的视图,可以完成什么样的用户请求。控制层并不做任何的数据处理。例如,用户点击一个连接,控制层接受请求后, 并不处理业务信息,它只把用户的信息传递给模型,告诉模型做什么,选择符合要求的视图返回给用户。因此,一个模型可能对应多个视图,一个视图可能对应多个模型。
1.4.MVC 优缺点
通过使用mvc框架采用了封装(分层)的思想,来降低耦合度,从而使我们的系统更灵活,扩展性更好。
那么小伙伴们肯定会问有什么优点呢?
MVC框架优点:
- 多视图共享一个模型,大大提高代码的可用性。
- 三个模块相互独立,改变其中之一不会影响其他俩,所以依据这种设计模式能构建良好的松耦合性的组件。
- 控制器提高了应用程序的灵活性和可控制性。控制器可以用来连接不同的模型和视图去完成用户的需求,这样控制器可以构造应用程序提高强有力的手段。
这就和我们Java很符合,代码可重用性、维护独立的组件,哪里bug找哪里!效率提高太多了。
这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。
有优点就肯定有缺点!接下来我们了解一下缺点吧。
MVC框架缺点:
- 增加了系统结构和实现的复杂性。对于简单页面,严格遵循mvc,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
- **视图与控制器过于紧密的连接。**视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
- **视图对模型数据的低效率访问。**依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
- 目前,一些高级的界面工具或构造器不支持mvc。
个人认为对于开发存在大量用户界面,并且业务逻辑复杂的大型应用程序,MVC将会使你的软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。总的来说MVC框架会让你的项目更加好维护,而不能局限在开发的时间更长上!!!
2.SpringMVC 框架构成
2.1.MVC框架流程图
执行过程中的组件的含义:
- 用户发送请求至前端控制器DispatcherServlet。
- DispatcherServlet收到请求调用HandlerMapping处理器映射器
- 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet通过HandlerAdapter处理器适配器调用处理器。
- 执行处理器(Controller,也叫后端控制器)。
- Controller执行完成返回ModelAndView。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
这里可以看出DispatherServlet一个中心组件,调配各组件之间的执行。
2.2.SpringMVC组件
- HandlerMapping
是用来查找Handler的。在SpringMVC中会有很多请求,每个请求都需要一个Handler处理,具体接收到一个请求之后使用哪个Handler进行处理,这就是HandlerMapping需要做的事。Handler存放了路径和方法。
- HandlerAdapter
从名字上看,它就是一个适配器。因为SpringMVC中的Handler可以是任意的形式,只要能处理请求就ok,但是Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情。
小结:Handler是用来干活的工具;HandlerMapping用于根据需要干的活找到相应的工具;HandlerAdapter是使用工具干活的人。
- ViewResolver
ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。这里就有两个关键问题:使用哪个模板?用什么技术(规则)填入参数?这其实是ViewResolver主要要做的工作,ViewResolver需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。
2.3.手写简单的MVC框架
先看一下大致的配置文件及各组件!!
先创建一个springboot项目
2.3.1.pox.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
<artifactId>smart_mvc</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!--读取XML文件 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- servlet的一些api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.2</version>
<scope>test</scope>
</dependency>
<!-- JSTL标签 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
</project>
web.xml配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>cn.wen.smart_mvc.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定前端控制器的配置文件-->
<init-param>
<param-name>configLocation</param-name>
<param-value>smart_mvc.xml</param-value>
</init-param>
<!-- 启动服务器自动将这个配置文件加载-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!-- url表示什么样的url可以提交到servlet类中-->
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
配置完了就可以先创建一个User实体类
2.3.2.创建User实体类
java
package cn.wen.smart_mvc.pojo;
public class User {
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
2.3.3.DispatcherServlet控制器
java
package cn.wen.smart_mvc.web.servlet;
import cn.wen.smart_mvc.web.common.HandleMapping;
import cn.wen.smart_mvc.web.common.Handler;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* 表示定义一个前端控制器,请求处理和加载配置映射
*/
public class DispatcherServlet extends HttpServlet {
private HandleMapping handleMapping = new HandleMapping();
/**
* 调用时机在处理请求之前,将编写的smart_mvc.xml配置文件导入
* 这里的init方法是生成该控制器的初始化 通过xml配置中的注入容器
* @throws ServletException
*/
@Override
public void init() throws ServletException {
// bean标签的class属性指向的类加载到这里(反射完成对象的创建)
// 1、加载读取smart_mvc.xml的配置文件
// 通过类加载源文件获取流
/**
* 采用在web.xml类配置初始化配置
*/
// InputStream is = getClass().getClassLoader().getResourceAsStream("smart_mvc.xml");
// 获取web.xml中 的configLocation的属性就可以在web.xml中修改,不用固定配置文件名
String configLocation = getServletConfig().getInitParameter("configLocation");
InputStream is = getClass().getClassLoader().getResourceAsStream(configLocation);
// 2、加载其中的数据(树状的结构),dom4j相关类和接口完成
SAXReader saxReader = new SAXReader();
try {
// 返回的是一个document 对象,是一个树状的结构
Document document = saxReader.read(is);
// 获取xml文件的根路劲
Element rootElement = document.getRootElement();
// 通过根路劲获取全部的子节点
List<Element> elements = rootElement.elements();//多个子节点
// 这个集合存储dom4j加载出来的bean对象
List<Object> beans = new ArrayList<Object>();
// 读取元素的目的是将class实现指向的路径的类加载成对象
for (Element element : elements) {
// 获取该元素的对象的属性,element相对于一个bean
String className = element.attributeValue("class");
System.out.println("className"+className);
// 根据这个类的路径来创建这个类的对象通过forName来完成 反射拿到注入的bean
Object bean = Class.forName(className).newInstance();//比如HelloController对象
// bean就是当前的对象,进行保存
beans.add(bean);
}
System.out.println("bean" + beans);
// 将beans传递个HandlerMapping来查找 将查找搭配的beans存入hanleMapping 返回
handleMapping.process(beans);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO
// 获取url截取路径来拼接前缀后缀
request.setCharacterEncoding("UTF-8");
// url请求路径,提前准备
// String uri = request.getRequestURL();// http://localhost:8080/smart_mvc//login
String uri = request.getRequestURI();// /smart_mvc/login
// 需要截取路径
System.out.println("uri"+uri);
// 获取当前项目的项目名
String contextPath = request.getContextPath();
System.out.println(contextPath);
String path = uri.substring(contextPath.length());
System.out.println("path"+path);
// 获取处理映射器的映射对象(通过url来获取)通过getHandler来获取HandlerMapping中的数据
Handler handler = handleMapping.getHandler(path);
// 通过handler来获取object和method
Object bean = handler.getObject();
Method method = handler.getMethod();
Object resultValue = null;
System.out.println(bean);
System.out.println(method);
try {
// 通过反射来执行方法
// resultValue = method.invoke(bean);
// 根据是否有参数来选择性使用方法 获取传入的参数
Class<?>[] parameterTypes = method.getParameterTypes();
if(parameterTypes.length>0){
// 含参数、创建临时数组(Object)
Object[] params = new Object[parameterTypes.length];
// 注意:目前只考虑请求2个参数:request 和 response 处理参数
for (int i = 0; i<parameterTypes.length;i++){
if(parameterTypes[i]==HttpServletRequest.class){
params[i] =request;
}
if(parameterTypes[i]==HttpServletResponse.class){
params[i] =response;
}
}
// 调用invoke方法
resultValue = method.invoke(bean, params);
}else {
// 不含参数
resultValue = method.invoke(bean);
}
// 方法的login redirect:/toSuccess
System.out.println("resultValue"+resultValue);
String viewName = resultValue.toString();
System.out.println(viewName);
// 重定向
if(viewName.startsWith("redirect:")){
// 将视图的名称拼接成完整的路径进行重定向:/smart_mvc/login
String redirectPath = contextPath +"/"+viewName.substring("redirect:".length());
response.sendRedirect(redirectPath);// 重定向操作
}else {// 转发
// 转发路径:父路径/login/+".jsp"
String jspPath = viewName + ".jsp";
// 完成转发操作
request.getRequestDispatcher(jspPath).forward(request,response);
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
2.3.4.配置文件
该配置文件可以将Controller注入bean容器中
xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!--表示注册一个处理器: Controller的一个类,class属性,表示一个具体的路径-->
<bean class="cn.wen.smart_mvc.controller.HelloController"></bean>
<bean class="cn.wen.smart_mvc.controller.LoginController"></bean>
</beans>
先将那些注解先写好等下直接用:
@MyController,@MyService,@MyRequesMapping,@RequestParam,@MyAutowired
java
// @MyAutowired注解代码:
import java.lang.annotation.*;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyAutowired {
String value() default "";
}
// @MyController注解代码:
package cn.wen.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyController {
String value() default "";
}
// @MyRequestMapping注解代码:
package cn.wen.annotation;
import java.lang.annotation.*;
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestMapping {
String value();
}
// @MyRequestParam注解代码
package cn.wen.annotation;
import java.lang.annotation.*;
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyRequestParam {
String value();
}
// @MyService注解代码
package cn.wen.annotation;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyService {
String value() default "";
}
上面的注解我还没有放入我这个简单的框架中
我主要用了下面几个
java
package cn.wen.smart_mvc.web.annotation;
import java.lang.annotation.*;
/**
* 映射规则:即表示那个请求和哪个对象的处理方法进行映射
*/
// 添加元注解进行进一步描述
@Documented
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)// 注解一直存在
public @interface RequestMapping {
// value 请求的url地址
String value() default "";
}
2.3.5.Handler处理器类
java
package cn.wen.smart_mvc.web.common;
import java.lang.reflect.Method;
/**
* 处理器:用来将请求的方法映射:类(那个类),方法(那个类的方法)
*/
public class Handler {
// 类处理器的对象(HelloController类)
private Object object;
// 请求处理的方法(HelloController.hello)
private Method method;
// 构造方法
public Handler(){
}
public Handler(Object object, Method method) {
this.object = object;
this.method = method;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}
2.3.6.Controller类
java
package cn.wen.smart_mvc.controller;
import cn.wen.smart_mvc.pojo.User;
import cn.wen.smart_mvc.web.annotation.RequestMapping;
import javax.servlet.http.HttpServletRequest;
public class LoginController {
// 登录页面
@RequestMapping("/toLogin")
public String toLogin(){
System.out.println("toLogin");
return "login";
}
// 登录我这里目前还没实现参数的注解
@RequestMapping("/login")
public String login(HttpServletRequest request){
String username = request.getParameter("username");
String password = request.getParameter("password");
System.out.println(username+password);
if(username.equals("tom") && password.equals("123456")){
return "redirect:toSuccess";
}else {
request.setAttribute("msg","用户名或者密码错误!");
}
// System.out.println(user.toString());
System.out.println("LoginController类的login()方法");
// return "success";直接进入success页面
// 重定向success页面
return "login";
}
// 跳转成功页面
@RequestMapping("/toSuccess")
public String toSuccess(){
System.out.println("跳转成功页面");
return "success";
}
}
2.3.7.HandleMapping
java
package cn.wen.smart_mvc.web.common;
import cn.wen.smart_mvc.web.annotation.RequestMapping;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 处理器映射,url地址和唯一的一个Handle进行映射(Controller映射)
*/
public class HandleMapping {
// 定义一个Map集合用于存储映射规则
private Map<String ,Handler> mappings = new HashMap<String, Handler>();
/**
* getHandler()用户获取映射中所有的结果内容,接受一个值为url
*
*/
public Handler getHandler(String path){
return mappings.get(path);
}
/**
* 将请求映射全部存到集合中
* 1.url地址
* 2.Controller(bean对象),将这个值以参数的形式传递
*/
public void process(List beans){
// 需要一个循环结构来拿到url的list
for (Object bean : beans){
// 将这个bean的请求方法获取对应的注解获取到
// TODO
// 反射为对应对象的实例对象
Class cls = bean.getClass();
// 获取该对象的全部方法
Method[] methods = cls.getDeclaredMethods();
// 每个方法对应的url路径
for (Method method : methods) {
// 判断当前的method对象所指向的方法是否被@RequestMapping修饰
// value ="hello" 获取注解的参数值,就是url
// 这里指定注解类型
RequestMapping requestMapping = method.getDeclaredAnnotation(RequestMapping.class);
// 请求url地址
String path = requestMapping.value();
// 获取Handler对象
Handler handler = new Handler(bean, method);
// 保存Handler对象
mappings.put(path,handler);
}
System.out.println("mappings"+mappings);
}
}
}
这样我们就简单手写了一个简易的SpringMVC框架了哦!!
2.4.另一个版本SpringMVC
2.4.1.pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<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>cn.wen</groupId>
<artifactId>springmvc-frame</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
2.4.2.web.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 前端控制器-->
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>cn.wen.springmvc.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- 初始化的配置文件的位置-->
<param-name>contextConfigLocation</param-name>
<!-- IOC容器的位置-->
<param-value>classpath:springmvc.xml</param-value>
</init-param>
<!-- Web服务器一旦驱动,Servlet就会实例化创建对象,然后初始化(预备创建对象)-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
2.4.3.springmvc.xml
xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<!-- 容器需要扫描的包-->
<component-scan base-package="cn.wen.baiqi.controller,cn.wen.baiqi.service"/>
</beans>
这里包下分了两个模块业务模块、框架模块
2.4.4.业务模块
Controller.UserController
java
package cn.wen.baiqi.controller;
import cn.wen.pojo.User;
import cn.wen.service.UserService;
import cn.wen.springmvc.annotation.*;
@Controller
public class UserController {
@AutoWired(value = "userService")
UserService userService;
// 定义方法
@RequestMapping("/findUser")
public String findUserInfo(){
// 调用服务层
userService.findUser();
return "success.jsp";
}
// 返回json格式数据
@RequestMapping("/getData")
@ResponseBody
public User getData(){
// 调用服务层
User user = userService.getUser();
return user;
}
// 返回json格式数据
@RequestMapping("/paramTest")
@ResponseBody
public User paramTest(@RequestParam("param") String param){
System.out.println(param);
// 调用服务层
User user = userService.getUser();
return user;
}
}
bean类
java
package cn.wen.baiqi.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String username;
private int age;
private String sex;
}
service.UserService接口
java
package cn.wen.baiqi.service;
import cn.wen.baiqi.pojo.User;
public interface UserService {
public void findUser();
public User getUser();
}
service.impl.UserServiceImpl实现类
java
package cn.wen.baiqi.service.impl;
import cn.wen.baiqi.pojo.User;
import cn.wen.baiqi.service.UserService;
import cn.wen.springmvc.annotation.Service;
@Service(value = "userService")
public class UserServiceImpl implements UserService {
@Override
public void findUser() {
System.out.println("=====查询用户信息======");
}
@Override
public User getUser() {
return new User("小明",18,"男");
}
}
2.4.5.springmvc框架模块
annotation注解
java
package cn.wen.springmvc.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
/**
* @Retention注解表示Annotation的保留策略
* RetentionPolicy.Class:运行时不保留,不可以通过反射读取
* RetentionPolicy.RUNTIME:运行时保留,可以反射读取
* RetentionPolicy.SOURCE:丢弃
*/
// 元注解 修饰注解的注解
// @interface AutoWired 这个叫注解
@Target(value = ElementType.FIELD)
@Retention(value = RetentionPolicy.RUNTIME) // 这个就是可以反射获取当前注解
public @interface AutoWired {
String value();
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Controller {
String value() default "";
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestBody {
boolean required() default true;
}
@Target(ElementType.METHOD) // 元注解
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestMapping {
String value() default "";
}
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
String value() default "";
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseBody {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Service {
String value() default "";
}
Handler类
java
package cn.wen.springmvc.handler;
import java.lang.reflect.Method;
/**
* Handler 是控制器中的handler
*/
public class Handler {
// 请求URL地址
private String url;
// 后台控制器
private Object controller;
// 控制器中指定的方法
private Method method;
public Handler(String url, Object controller, Method method) {
this.url = url;
this.controller = controller;
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
}
xml的配置文件标签解析
java
package cn.wen.springmvc.xml;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.InputStream;
public class XmlParse {
public static String getBasePackage(String xml) {
try {
SAXReader saxReader = new SAXReader();
InputStream inputStream = XmlParse.class.getClassLoader().getResourceAsStream(xml);
// XML文档对象
Document document = saxReader.read(inputStream);
Element rootElement = document.getRootElement();
// 获取这个注解
Element componentScan = rootElement.element("component-scan");
// 获取该注解的参数
Attribute attribute = componentScan.attribute("base-package");
// 获取参数内容
String basePackage = attribute.getText();
return basePackage;
} catch (DocumentException e) {
e.printStackTrace();
}
return "";
}
}
前端控制器
java
package cn.wen.springmvc.servlet;
import cn.wen.springmvc.annotation.Controller;
import cn.wen.springmvc.annotation.RequestMapping;
import cn.wen.springmvc.annotation.ResponseBody;
import cn.wen.springmvc.context.WebApplicationContext;
import cn.wen.springmvc.handler.Handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.*;
public class DispatcherServlet extends HttpServlet {
// 指定SpringMVC容器
private WebApplicationContext webApplicationContext;
// 创建集合 用于存放 映射关系 映射地址 与 控制器.方法 用于请求注解从该集合进行匹配p
List<Handler> handleMapping = new ArrayList<>();
@Override
public void init() throws ServletException {
// 1、加载初始化参数 classpath:springmvc.xml
String contextConfigLocation = this.getServletConfig().getInitParameter("contextConfigLocation");
// 2、创建SpringMVC容器
webApplicationContext = new WebApplicationContext(contextConfigLocation);
// 3、进行初始化操作
webApplicationContext.onRefresh();
// 4、初始化请求映射关系 /findUser ===> 控制器.方法 映射
initHandlerMapping();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 进行请求分发处理
doDispatcher(req, resp);
}
// 进行请求分发处理
private void doDispatcher(HttpServletRequest req, HttpServletResponse resp) {
// 根据用户的请求地址 /findUser 查找Handler | Controller
Handler handler = getHandler(req);
try {
if (handler == null){
resp.getWriter().print("<h1>404 NOT FOUND!</h1>");
}else {
// 1、调用处理方法之前 这里需要先获取前端获取的参数列表
// 反射获取全部的参数列表
//获取方法的参数列表
Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
//获取请求的参数
Map<String, String[]> parameterMap = req.getParameterMap();
//保存参数值
Object [] paramValues= new Object[parameterTypes.length];
//方法的参数列表
for (int i = 0; i<parameterTypes.length; i++){
//根据参数名称,做某些处理
String requestParam = parameterTypes[i].getSimpleName();
if (requestParam.equals("HttpServletRequest")){
//参数类型已明确,这边强转类型
paramValues[i]=req;
continue;
}
if (requestParam.equals("HttpServletResponse")){
paramValues[i]=resp;
continue;
}
if(requestParam.equals("String")){
for (Map.Entry<String, String[]> param : parameterMap.entrySet()) {
String value =Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
paramValues[i]=value;
}
}
}
// 调用目标方法
Object result = handler.getMethod().invoke(handler.getController(),paramValues);
// 2、进行请求跳转 这里需要判断 到达是重定向还是 转发
// 返回的是String类型
if (result instanceof String){
// 跳转JSP
String viewName = (String) result;
// 重定向
if (viewName.contains(":")){
String viewType = viewName.split(":")[0];
String viewPage = viewName.split(":")[1];
if (viewName.equals("forward")){
// 转发
req.getRequestDispatcher(viewPage).forward(req,resp);
}else {
// 重定向 redirect:/user.jsp
resp.sendRedirect(viewPage);
}
}else {
// 默认就转发
req.getRequestDispatcher(viewName).forward(req,resp);
}
}else {
// 返回JSON格式的数据
Method method = handler.getMethod();
if (method.isAnnotationPresent(ResponseBody.class)){
// 将数据转换成JSON格式数据
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(result);
resp.setContentType("text/html;charset=utf-8");
// 将json通过字符流写出去
PrintWriter writer = resp.getWriter();
writer.print(json);
writer.flush();
writer.close();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
private Object convert(Class<?> type, String value) {
if(Integer.class == type){
return Integer.valueOf(value);
}
return value;
}
// 根据用户球球查找对应的Handler
private Handler getHandler(HttpServletRequest req) {
// /findUser
String requestURI = req.getRequestURI();
for (Handler handler : handleMapping) {
// 这里就是遍历查找匹配的path 找到满足条件的Handler
if (handler.getUrl().equals(requestURI)){
return handler;
}
}
return null;
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
}
// 初始化请求映射关系 只存在控制层
private void initHandlerMapping() {
for (Map.Entry<String, Object> entry : webApplicationContext.iocMap.entrySet()) {
// 先拿到bean 获取class类型
Class<?> clazz = entry.getValue().getClass();
//
if (clazz.isAnnotationPresent(Controller.class)){
// 是否存在Controller注解 利用反射来实现映射
// 获取该bean中的所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 获取方法的注解
// 是否存在RequestMapping类型的注解
if (method.isAnnotationPresent(RequestMapping.class)){
RequestMapping requestMappingAno = method.getAnnotation(RequestMapping.class);
// 获取注解中的值 /findUser
String url = requestMappingAno.value();
// 建立 映射地址 与控制器.方法
Handler handler = new Handler(url, entry.getValue(), method);
handleMapping.add(handler);
}
}
}
}
}
}
3.SpringMVC底层结构
java
package cn.wen.springmvc.context;
import cn.wen.springmvc.annotation.AutoWired;
import cn.wen.springmvc.annotation.Controller;
import cn.wen.springmvc.annotation.Service;
import cn.wen.springmvc.xml.XmlParse;
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* SpringMVC容器
*/
public class WebApplicationContext {
// classpath:springmvc.xml
String contextConfigLocation;
// 定义集合 存放bean的权限名 | 包名 : 类名
List<String> classNameList = new ArrayList<>();
// 创建Map集合用于扮演IOC容器:key存入bean名字 value存放bean实例
public Map<String, Object> iocMap = new ConcurrentHashMap<>();
public WebApplicationContext() {
}
public WebApplicationContext(String contextConfigLocation) {
this.contextConfigLocation = contextConfigLocation;
}
// 进行初始化
// 将springmvc.xml扫描的包反射到初始化中
public void onRefresh(){
System.out.println("进入onRefresh方法中");
// 1、进行解析springmvc.xml中的配置文件操作 cn.wen.baiqi.controller,cn.wen.baiqi.service
// 截取springmvc.xml的路径
String xml = contextConfigLocation.split(":")[1];
String pack = XmlParse.getBasePackage(xml);
String[] packs = pack.split(",");
// 2、进行包扫描 cn.wen.baiqi.controller cn.wen.baiqi.service
// 将一些类扫描到
for (String pa : packs) {
executeScanPackage(pa);
}
// 3、实例化容器中的bean
executeInstance();
// 4、进行自动注入操作
executeAutoWried();
}
// 进行自动注入操作
private void executeAutoWried() {
// Controller 中的自动注入方法的实现
try {
// 容器中取出bean,然后判断bean中是否有属性使用AutoWired 如果使用了直接,就需要自动注入操作
for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
// 获取容器中的bean
Object bean = entry.getValue();
// 获取bean中的属性
Field[] fields = bean.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(AutoWired.class)){
// 获取注解中的value 该值就是bean的name 注入操作
AutoWired autoWiredAno = field.getAnnotation(AutoWired.class);
String beanName = autoWiredAno.value();
// 进行注入操作 取消检查机制 不能正常注入
field.setAccessible(true);
field.set(bean,iocMap.get(beanName));
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
/**
* 实例化容器中的bean
*/
private void executeInstance() {
try {
// 存放的包名 类名信息 利用反射获取
for (String className : classNameList) {
Class<?> clazz = Class.forName(className);
if (clazz.isAnnotationPresent(Controller.class)){
// 控制层的bean
String beanName = clazz.getSimpleName().substring(0,1).toLowerCase()+clazz.getSimpleName().substring(1);
iocMap.put(beanName,clazz.newInstance());
}else if (clazz.isAnnotationPresent(Service.class)){
// Service 层 bean 将注解的value的名字作为bean名称
Service serviceAn = clazz.getAnnotation(Service.class);
String beanName = serviceAn.value();
iocMap.put(beanName,clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 扫描包
*/
public void executeScanPackage(String pack){
// /cn/wen/service
URL url = this.getClass().getClassLoader().getResource("/" + pack.replaceAll("\\.", "/"));
String path = url.getFile();
File dir = new File(path);
System.out.println(dir);
for (File f : dir.listFiles()) {
if (f.isDirectory()){
// 当前是一个文件目录 cn.wen.service.impl
executeScanPackage(pack+"."+f.getName());
}else {
// 文件目录下的文件 获取全路径 获取到的是UserController.class 全名
String className = pack + "." + f.getName().replaceAll(".class", "");
classNameList.add(className);
}
}
}
}
3.1.配置文件
Spring Framework本身没有Web功能,Spring MVC使用WebApplicationContext类扩展ApplicationContext ,使得拥有web功能。那么,Spring MVC是如何在web环境中创建IOC容器呢?web环境中的IOC容器的结构又是什么结构呢?web环境中,Spring IOC容器是怎么启动呢?
以Tomcat为例,在Web容器中使用Spirng MVC,必须进行四项的配置:
- 修改web.xml,添加servlet定义;
- 编写servletname-servlet.xml(servletname是在web.xm中配置DispactherServlet时使servlet-name的值)配置;
- contextConfigLocation 初始化参数、配置ContextLoaderListerner;
Web.xml配置如下:
xml
<!-- servlet定义:前端处理器,接受的HTTP请求和转发请求的类 当ioc容器创建成功就会创建该容器-->
<servlet>
<servlet-name>court</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!-- court-servlet.xml:定义WebAppliactionContext上下文中的bean -->
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:court-servlet.xml</param-value>
</init-param>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>court</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<!-- 配置contextConfigLocation初始化参数:指定Spring IOC容器需要读取的定义了
非web层的Bean(DAO/Service)的XML文件路径 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/court-service.xml</param-value>
</context-param>
<!-- 配置ContextLoaderListerner:Spring MVC在Web容器中的启动类,
负责Spring IOC容器在Web上下文中的初始化 其中会init创建一些容器 XmlWebApplicaitonContext
就是IOC容器 XmlWebApplicaitonContext 配置文件-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ContextLoaderListener的作用就是启动Web容器时,读取在contextConfigLocation中定义的xml文件,自动装配ApplicationContext的配置信息,并产生WebApplicationContext对象,然后将这个对象放置在ServletContext的属性里,这样我们只要得到Servlet就可以得到WebApplicationContext对象,并利用这个对象访问spring容器管理的bean。
简单来说,就是上面这段配置为项目提供了spring支持,初始化了IOC容器。
在web.xml配置文件中,有两个主要的配置:ContextLoaderListener和DispatcherServlet。同样的关于spring配置文件的相关配置也有两部分:context-param和DispatcherServlet中的init-param。那么,这两部分的配置有什么区别呢?它们都担任什么样的职责呢?
在Spring MVC中,Spring Context是以父子的继承结构存在的。Web环境中存在一个ROOT Context,这个Context是整个应用的根上下文,是其他context的双亲Context。同时Spring MVC也对应的持有一个独立的Context,它是ROOT Context的子上下文。
对于这样的Context结构在Spring MVC中是如何实现的呢?下面就先从ROOT Context入手,ROOT Context是在ContextLoaderListener中配置的,ContextLoaderListener读取context-param中的contextConfigLocation指定的配置文件,创建ROOT Context。
Spring MVC启动过程大致分为两个过程:
- ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext(WEB容器)中;
- DispatcherServlet初始化(执行init方法来创建前端中央控制器);
启动Web容器,执行流程如下
- 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml ,读两个节点: 和 ;
- 紧接着,容器创建一个ServletContext (上下文),在该应用内全局共享;
- 容器将转化为键值对,并交给ServletContext;
- 容器创建中的类实例,即创建监听,该监听器必须实现自ServletContextListener接口,如Log4jConfigListener,或者如上自定义实现类(如果不自定义实现,可以使用实现类ContextLoaderListener)
- Web项目启动中,在监听类中contextInitialized(ServletContextEvent event)初始化方法会被执行,在该方法中获取到ServletContext和全局参数;
- 得到这个context-param的值之后,你就可以做一些操作了。这个时候你的WEB项目还没有完全启动完成,这个动作会比所有的Servlet都要早。换句话说,这个时候,你对中的键值做的操作,将在你的WEB项目完全启动之前被执行。
- Web项目结束时,监听类中的contextDestroyed(ServletContextEvent event)方法会被执行;
简单来说流程就是:1、读配置文件节点 --> 2、创建ServletContext --> 3、设置参数到Context中 --> 4、监听listener并执行初始化方法和销毁方法。
3.2.Web容器
Web容器调用contextInitialized方法初始化ContextLoaderListener,在此方法中,ContextLoaderListener通过调用继承自ContextLoader的initWebApplicationContext方法实例化Spring Ioc容器。
先看一下WebApplicationContext是如何扩展ApplicationContext来添加对Web环境的支持的。WebApplicationContext接口定义如下:
java
public interface WebApplicationContext extends ApplicationContext {
// 根上下文在ServletContext中的名称
String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
// 取得web容器的ServletContext
ServletContext getServletContext();
}
下面看一下 ContextLoaderListener 中创建 context 的源码:ContextLoader.java
java
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
// PS : ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE=WebApplicationContext.class.getName() + ".ROOT" 根上下文的名称
// PS : 默认情况下,配置文件的位置和名称是: DEFAULT_CONFIG_LOCATION = "/WEB-INF/applicationContext.xml"
// 在整个web应用中,只能有一个根上下文
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!");
}
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
// 在这里执行了创建WebApplicationContext的操作
// 是否存在Web容器(根容器)
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
// PS: 将根上下文放置在servletContext中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
} catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
} catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
再看一下WebApplicationContext对象是如何创建的:ContextLoader.java
java
protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) {
// 根据web.xml中的配置决定使用何种WebApplicationContext。默认情况下使用XmlWebApplicationContext(IOC容器)
// web.xml中相关的配置context-param的名称"contextClass"
Class<?> contextClass = determineContextClass(sc);
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Custom context class [" + contextClass.getName() + "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]");
}
// 实例化WebApplicationContext的实现类
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
// Assign the best possible id value.
if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) {
// Servlet <= 2.4: resort to name specified in web.xml, if any.
String servletContextName = sc.getServletContextName();
if (servletContextName != null) {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + servletContextName);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX);
}
} else {
// Servlet 2.5's getContextPath available!
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + sc.getContextPath());
}
wac.setParent(parent);
wac.setServletContext(sc);
// 设置spring的配置文件
wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM));
customizeContext(sc, wac);
// spring容器初始化
wac.refresh();
return wac;
}
3.3.SpringMVC的上下文加载和初始化
Spring MVC中核心的类是DispatcherServlet,在这个类中完成Spring context的加载与创建,并且能够根据Spring Context的内容将请求分发给各个Controller类。DispatcherServlet继承自HttpServlet,关于Spring Context的配置文件加载和创建是在init()方法中进行的,主要的调用顺序是 init --> initServletBean --> initWebApplicationContext。
- 先来看一下initWebApplicationContext的实现:FrameworkServlet.java
java
protected WebApplicationContext initWebApplicationContext() {
// 先从web容器的ServletContext中查找WebApplicationContext
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
// No fixed context defined for this servlet - create a local one.
// 从ServletContext中取得根上下文
WebApplicationContext parent = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// 创建Spring MVC的上下文,并将根上下文作为起双亲上下文
wac = createWebApplicationContext(parent);
}
if (!this.refreshEventReceived) {
// Apparently not a ConfigurableApplicationContext with refresh support:
// triggering initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
// 取得context在ServletContext中的名称
String attrName = getServletContextAttributeName();
//将Spring MVC的Context放置到ServletContext中
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + "' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
通过initWebApplicationContext方法的调用,创建了DispatcherServlet对应的context,并将其放置到ServletContext中,这样就完成了在web容器中构建Spring IoC容器的过程。
3.4.Spring中DispatcherServlet、WebApplicationContext、ServletContext的关系
要想很好理解这三个上下文的关系,需要先熟悉Spring是怎样在web容器中启动起来的。Spring的启动过程其实就是其IOC容器的启动过程,对于web程序,IOC容器启动过程即是建立上下文的过程。
Spring的启动过程:
- 首先,对于一个web应用,其部署在web容器中 ,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext ,其为后面的spring IoC容器提供宿主环境;
- 其次,在web.xml中会提供有contextLoaderListener 。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized 方法会被调用,在这个方法中,spring会初始化一个启动上下文 ,这个上下文被称为根上下文,即WebApplicationContext IOC容器,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
- 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet ,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。
- DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。
- 初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。