Spring MVC底层分析

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框架优点:

  1. 多视图共享一个模型,大大提高代码的可用性。
  2. 三个模块相互独立,改变其中之一不会影响其他俩,所以依据这种设计模式能构建良好的松耦合性的组件。
  3. 控制器提高了应用程序的灵活性和可控制性。控制器可以用来连接不同的模型和视图去完成用户的需求,这样控制器可以构造应用程序提高强有力的手段。

这就和我们Java很符合,代码可重用性、维护独立的组件,哪里bug找哪里!效率提高太多了。

这三层是紧密联系在一起的,但又是互相独立的,每一层内部的变化不影响其他层。每一层都对外提供接口(Interface),供上面一层调用。这样一来,软件就可以实现模块化,修改外观或者变更数据都不用修改其他层,大大方便了维护和升级。

有优点就肯定有缺点!接下来我们了解一下缺点吧。
MVC框架缺点:

  1. 增加了系统结构和实现的复杂性。对于简单页面,严格遵循mvc,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
  2. **视图与控制器过于紧密的连接。**视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
  3. **视图对模型数据的低效率访问。**依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
  4. 目前,一些高级的界面工具或构造器不支持mvc。

个人认为对于开发存在大量用户界面,并且业务逻辑复杂的大型应用程序,MVC将会使你的软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。总的来说MVC框架会让你的项目更加好维护,而不能局限在开发的时间更长上!!!

2.SpringMVC 框架构成

2.1.MVC框架流程图


执行过程中的组件的含义:

  1. 用户发送请求至前端控制器DispatcherServlet。
  2. DispatcherServlet收到请求调用HandlerMapping处理器映射器
  3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
  4. DispatcherServlet通过HandlerAdapter处理器适配器调用处理器。
  5. 执行处理器(Controller,也叫后端控制器)。
  6. Controller执行完成返回ModelAndView。
  7. HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
  8. DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
  9. ViewReslover解析后返回具体View。
  10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
  11. 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启动过程大致分为两个过程:

  1. ContextLoaderListener初始化,实例化IoC容器,并将此容器实例注册到ServletContext(WEB容器)中;
  2. DispatcherServlet初始化(执行init方法来创建前端中央控制器);

启动Web容器,执行流程如下

  1. 启动一个WEB项目的时候,容器(如:Tomcat)会去读它的配置文件web.xml ,读两个节点: 和 ;
  2. 紧接着,容器创建一个ServletContext (上下文),在该应用内全局共享
  3. 容器将转化为键值对,并交给ServletContext
  4. 容器创建中的类实例,即创建监听,该监听器必须实现自ServletContextListener接口,如Log4jConfigListener,或者如上自定义实现类(如果不自定义实现,可以使用实现类ContextLoaderListener)
  5. Web项目启动中,在监听类中contextInitialized(ServletContextEvent event)初始化方法会被执行,在该方法中获取到ServletContext和全局参数;
  6. 得到这个context-param的值之后,你就可以做一些操作了。这个时候你的WEB项目还没有完全启动完成,这个动作会比所有的Servlet都要早。换句话说,这个时候,你对中的键值做的操作,将在你的WEB项目完全启动之前被执行。
  7. 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。

  1. 先来看一下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的启动过程:

  1. 首先,对于一个web应用,其部署在web容器中 ,web容器提供其一个全局的上下文环境,这个上下文就是ServletContext ,其为后面的spring IoC容器提供宿主环境;
  2. 其次,在web.xml中会提供有contextLoaderListener 。在web容器启动时,会触发容器初始化事件,此时contextLoaderListener会监听到这个事件,其contextInitialized 方法会被调用,在这个方法中,spring会初始化一个启动上下文 ,这个上下文被称为根上下文,即WebApplicationContext IOC容器,这是一个接口类,确切的说,其实际的实现类是XmlWebApplicationContext。这个就是spring的IoC容器,其对应的Bean定义的配置由web.xml中的context-param标签指定。在这个IoC容器初始化完毕后,spring以WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE为属性Key,将其存储到ServletContext中,便于获取;
  3. 再次,contextLoaderListener监听器初始化完毕后,开始初始化web.xml中配置的Servlet ,这个servlet可以配置多个,以最常见的DispatcherServlet为例,这个servlet实际上是一个标准的前端控制器,用以转发、匹配、处理每个servlet请求。
  4. DispatcherServlet上下文在初始化的时候会建立自己的IoC上下文,用以持有spring mvc相关的bean。在建立DispatcherServlet自己的IoC上下文时,会利用WebApplicationContext.ROOTWEBAPPLICATIONCONTEXTATTRIBUTE先从ServletContext中获取之前的根上下文(即WebApplicationContext)作为自己上下文的parent上下文。有了这个parent上下文之后,再初始化自己持有的上下文。这个DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化处理器映射、视图解析等。这个servlet自己持有的上下文默认实现类也是mlWebApplicationContext。
  5. 初始化完毕后,spring以与servlet的名字相关(此处不是简单的以servlet名为Key,而是通过一些转换,具体可自行查看源码)的属性为属性Key,也将其存到ServletContext中,以便后续使用。这样每个servlet就持有自己的上下文,即拥有自己独立的bean空间,同时各个servlet共享相同的bean,即根上下文(第2步中初始化的上下文)定义的那些bean。
相关推荐
seasugar3 分钟前
Maven怎么会出现一个dependency-reduced-pom.xml的文件
xml·java·maven
一只淡水鱼666 分钟前
【mybatis】基本操作:详解Spring通过注解和XML的方式来操作mybatis
java·数据库·spring·mybatis
唐叔在学习24 分钟前
【唐叔学算法】第19天:交换排序-冒泡排序与快速排序的深度解析及Java实现
java·算法·排序算法
music0ant28 分钟前
Idea 配置环境 更改Maven设置
java·maven·intellij-idea
记得开心一点嘛43 分钟前
Nginx与Tomcat之间的关系
java·nginx·tomcat
界面开发小八哥1 小时前
「Java EE开发指南」如何用MyEclipse构建一个Web项目?(一)
java·前端·ide·java-ee·myeclipse
王伯爵1 小时前
<packaging>jar</packaging>和<packaging>pom</packaging>的区别
java·pycharm·jar
Q_19284999061 小时前
基于Spring Boot的个人健康管理系统
java·spring boot·后端
Q_19284999061 小时前
基于Springcloud的智能社区服务系统
后端·spring·spring cloud
m0_748245172 小时前
Web第一次作业
java