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。
相关推荐
louisgeek8 分钟前
Java 位运算
java
hweiyu001 小时前
Maven 私库
java·maven
Boilermaker19921 小时前
【Java EE】SpringIoC
前端·数据库·spring
Super Rookie1 小时前
Spring Boot 企业项目技术选型
java·spring boot·后端
写不出来就跑路1 小时前
Spring Security架构与实战全解析
java·spring·架构
ZeroNews内网穿透2 小时前
服装零售企业跨区域运营难题破解方案
java·大数据·运维·服务器·数据库·tcp/ip·零售
sleepcattt2 小时前
Spring中Bean的实例化(xml)
xml·java·spring
lzzy_lx_20892 小时前
Spring Boot登录认证实现学习心得:从皮肤信息系统项目中学到的经验
java·spring boot·后端
Dcs2 小时前
立即卸载这些插件,别让它们偷你的资产!
java
小七mod2 小时前
【Spring】Java SPI机制及Spring Boot使用实例
java·spring boot·spring·spi·双亲委派