Java SpringMVC 万字长文基础知识总结《上》-王者笔记建议收藏

2021/7/21 17:07:29

本文主要是介绍Java SpringMVC 万字长文基础知识总结《上》-王者笔记建议收藏,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

一 MVC设计模式简介

1.1 MVC优缺点

二 Spring MVC是什么

三 创建一个Spring MVC程序

   3.1 创建项目需要依赖的jar

  3.2后台controller测试代码

四  Spring MVC和Struts2的区别

4.1. 机制:

4.2. 性能:

4.3 参数传递:

五  Spring MVC视图解析器

5.1 什么是视图解析器

5.2 视图解析器的处理流程

5.3 视图解析器返回类型

5.4视图解析器的配置

5.4.1.返回物理视图的配置

5.4.2.返回模板的视图解析器的配置

六  Spring MVC执行流程

6.1 SpringMVC整个流程图:

6.2 具体业务流程:

6.3 关键组件说明:

1)DispatcherServlet

2)HandlerMapping

3)HandlerAdapter

4)Handler

5)View Resolver

6)View

七 Spring MVC @Controller和@RequestMapping

7.1、@Controller注解

7.2、@RequestMapping注解

7.2.1 @RequestMapping注解的使用

7.2.2 @RequestMapping注解提供的属性

7.2.3 method属性

 7.2.4 headers属性

7.2.5 consumes属性

7.2.6 produces属性

八 Spring MVCh获取参数的几种方式

8.1 普通方式-请求参数名和Controller方法的参数一致

后台代码示例:

8.2 对象方式-请求参数名和Controller方法中的对象的参数一致

后台代码示例:

8.3 自定义方法参数名-当请求参数名与方法参数名不一致时

后台代码示例:

8.4 HttpServletRequest方式

后台代码示例:

8.5 @PathVariable获取路径中的参数接收

后台代码示例:

8.6 @RequestBody-JSON方式接收

后台代码示例:

 九 Spring MVC重定向和转发

9.1 请求转发和重定向的区别

9.2 SpringMVC中实现转发和重定向

9.3 代码实现转发和重定向

十 Spring MVC @Autowired和@Service注解

10.1 @Autowired

10.2 BeanPostProcessor

10.3 装配过程

10.4 Bean 工厂

10.5 实例化与装配

10.6 执行装配

10.7 结束

参考文章和目录:

今天就分享到这儿吧,小伙伴们点赞、收藏、评论,下期见~~

一 MVC设计模式简介

MVC设计模式(Model-View-Controller)是常见的一种软件架构模式,

 该模式把软件系统分为三个基本部分:模型(Model)、视图(View)和 控制器(Controller)

使用MVC模式有很多优势,例如:简化后期对项目的修改、扩展等维护操作;使项目的某一部分变得可以重复利用;使项目的结构更加直观。

         具体来讲,MVC模式可以将项目划分为模型(M)、视图(V)和控制器(C)三分部分,并赋予各个部分不同的功能,方便开发人员进行分组。

       (1)视图(View):负责界面的显示,以及与用户的交互功能,例如表单、网页等。

       (2)控制器(Controller):可以理解为一个分发器,用来决定对于视图发来的请求,需要用哪一个模型来处理,以及处理完以后需要跳回到哪一个视图。即用来连接视图和模型。

          实际开发中,通常用控制器对客户端的请求数据进行封装(如将form表单发来的若干个表单字段值,封装到一个实体对象中),然后调用某一个模型来处理此请求,最后再转发请求(或重定向)到视图(或另一个控制器)。    

       (3)模型(Model):模型持有所有的数据、状态和程序逻辑。模型接受视图数据的请求,并返回最终的处理结果。


1.1 MVC优缺点

  • 1、 MVC的优点
            (1) 可以为一个模型在运行时同时建立和使用多个视图。变化-传播机制可以确保所有相关的视图及时得到模型数据变化,从而使所有关联的视图和控制器做到行为同步。
            (2) 视图与控制器的可接插性,允许更换视图和控制器对象,而且可以根据需求动态的打开或关闭、甚至在运行期间进行对象替换。
            (3) 模型的可移植性。因为模型是独立于视图的,所以可以把一个模型独立地移植到新的平台工作。需要做的只是在新平台上对视图和控制器进行新的修改。
            (4) 潜在的框架结构。可以基于此模型建立应用程序框架,不仅仅是用在设计界面的设计中。
    2、 MVC的不足之处
            (1) 增加了系统结构和实现的复杂性。对于简单的界面,严格遵循MVC,使模型、视图与控制器分离,会增加结构的复杂性,并可能产生过多的更新操作,降低运行效率。
            (2) 视图与控制器间的过于紧密的连接。视图与控制器是相互分离,但确实联系紧密的部件,视图没有控制器的存在,其应用是很有限的,反之亦然,这样就妨碍了他们的独立重用。
            (3) 视图对模型数据的低效率访问。依据模型操作接口的不同,视图可能需要多次调用才能获得足够的显示数据。对未变化数据的不必要的频繁访问,也将损害操作性能。
            (4) 目前,一般高级的界面工具或构造器不支持MVC模式。改造这些工具以适应MVC需要和建立分离的部件的代价是很高的,从而造成使用MVC的困难。

二 Spring MVC是什么

1.SpringMVC 是一种基于 Java 的实现 MVC 设计模型的请求驱动类型的轻量级 Web 框架,属于SpringFrameWork的后续产品,已经融合在 Spring Web Flow 中。 

2.SpringMVC = Spring +MVC

  •  spring mvc类似于struts的一个MVC开框架,其实都是属于spring,spring mvc需要有spring的架包作为支撑才能跑起来.

spring是一个一站式的框架,提供了表现层(springmvc)到业务层(spring)再到数据层(springdata)的全套解决方案;spring的两大核心IOC(控制反转)和AOP(面向切面编程)更是给我们的程序解耦和代码的简介提供了支持。

Spring框架图:

三 创建一个Spring MVC程序

   3.1 创建项目需要依赖的jar

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.linpeng</groupId>
    <artifactId>SpringDemo</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>SpringDemo Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <!-- spring需要的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.2.4.RELEASE</version>
            <type>jar</type>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.2.4.RELEASE</version>
            <type>jar</type>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>3.2.4.RELEASE</version>
            <type>jar</type>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>3.2.4.RELEASE</version>
            <type>jar</type>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>3.2.4.RELEASE</version>
            <type>jar</type>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.8.7</version>
        </dependency>



        <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.8.7</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>SpringDemo</finalName>
    </build>
</project>

  3.2后台controller测试代码

package com.linpeng.controller;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import javax.xml.crypto.Data;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.ServletRequestDataBinder;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import com.linpeng.java.Student;

@Controller
public class HelloController {

    /*
     * @RequestMapping("/test") public ModelAndView doAndView(){ ModelAndView mv
     * =new ModelAndView("hello"); mv.addObject("spring", "spring mvc");
     * //mv.setViewName("hello"); mv.addObject("key",new Date()); return mv; }
     * 
     * 拦截 /test 时 会找到 hello.jsp
     */

    // 拦截 /hello 时 默认会找到 hello.jsp
    @RequestMapping("/hello")
    public ModelAndView hello() {
        ModelAndView mv = new ModelAndView();
        mv.addObject("spring", "spring mvc");
        // mv.setViewName("hello");
        mv.addObject("key", new Date());
        return mv;
    }

    /*
     * http://localhost:8080/SpringDemo/ok
     * 对应转到 “hello.jsp”
     */
    @RequestMapping("/ok")
    public String randomMethodName() {
        return "hello";
    }

    /*
     * 四,自动匹配参数
     * http://localhost:8080/SpringDemo/param?name=linpeng&age=19 
     * 跳转到hello.jsp 方法里面会接收到参数name age
     * 打印 ====linpeng==19
     * */
    @RequestMapping("/param")
    public String paramMethod(String name,Integer age){
        System.out.println("===="+name+"=="+age);
        return "hello";
    }
/*
 * 五、自动装箱
 * 创建Student对象 属性名:name,age
 * http://localhost:8080/SpringDemo/student?name=linpeng&age=19
 * 打印 linpeng 19
 * 
 * ps:前提是Student的属性一定生成要get set否则注入失败
 */
    @RequestMapping("/student")
    public String student(Student stu){
        System.out.println(stu.name+" "+stu.age);
        return "hello";
    }
    /*
     * 向前台传递参数
     * http://localhost:8080/SpringDemo/show
     * show.jsp:<body>hello:${p.name} </body>
     */
    @RequestMapping("/show")
    public String date(Map<String, Object>map){
        Student student = new Student();
        map.put("p",student);
        student.setAge(20);
        student.setName("linpeng"); 
        return "show";
    }
    /*
     * 重定向跳转到show.jsp
     */
    @RequestMapping("/redirect")
    public  String redirect(){
        return "redirect:show";
    }

    /*
     * 处理局部异常(Controller内)
     * 该控制器里面 捕获异常对应的(跳转error.jsp)处理
     */
    @ExceptionHandler
    public ModelAndView exceptionHandler(Exception ex){
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("exception", ex);
        System.out.println("in testExceptionHandler");
        return mv;
    }
    //测试上面的异常
    @RequestMapping("/error")
    public String error(){
        int i = (int) (5/0);//抛出异常
        return "hello";
    }



}

四  Spring MVC和Struts2的区别

4.1. 机制:

spring mvc的入口是servlet,而struts2是filter,这样就导致了二者的机制不同。

4.2. 性能:

 spring会稍微比struts快。spring mvc是基于方法的设计,而sturts是基于类,每次发一次请求都会实例一个action,每个action都会被注入属性,而spring基于方法,粒度更细,但要小心把握像在servlet控制数据一样。spring3 mvc是方法级别的拦截,拦截到方法后根据参数上的注解,request数据注入进去,在spring3 mvc中,一个方法对应一个request上下文。而struts2框架是类级别的拦截,每次来了请求就创建一个Action,然后调用setter getter方法把request中的数据注入;struts2实际上是通setter getter方法与request打交道的;struts2中,一个Action对象对应一个request上下文。

4.3 参数传递:

struts是在接受参数的时候,可以用属性来接受参数,这就说明参数是让多个方法共享的。struts更加符合oop(面向对象编程)的编程思想, spring就比较谨慎,在servlet上扩展。

五  Spring MVC视图解析器

5.1 什么是视图解析器

视图解析器是用来接收经过处理器适配器调用具体的controller后生成的逻辑视图的,它接受 DispatcherServlet传过来的ModelAndView,然后将ModelAndView数据填充到相应的视图中,然后返回一个带有数据的视图再传给DispatcherServlet.

5.2 视图解析器的处理流程

1、调用目标方法,SpringMVC将目标方法返回的String、View、ModelMap或是ModelAndView都转换为一个ModelAndView对象;
2、然后通过视图解析器(ViewResolver)对ModelAndView对象中的View对象进行解析,将该逻辑视图View对象解析为一个物理视图View对象;
3、最后调用物理视图View对象的render()方法进行视图渲染,得到响应结果。

5.3 视图解析器返回类型

视图解析器不仅可以返回物理视图,还可以返回一个模板。

它会解析逻辑视图配置,返回一种Freemarker模板或者thymelea模板,该模板负责将数据模型中的数据合并到模板中,从而生成标准的输出,可以生成各种文本,包括HTML,XML,java源码等。

5.4视图解析器的配置

5.4.1.返回物理视图的配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp"></property>
        <property name="suffix" value=".jsp"></property>
    </bean>

5.4.2.返回模板的视图解析器的配置

<!--视图解析器-->
    <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerViewResolver">
        <property name="prefix" value="fm_"></property>
        <property name="suffix" value=".ftl"></property>
        <property name="order" value="1"></property>
    </bean>
<!--模板文件的路径以及其他配置-->
    <bean class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
        <property name="templateLoaderPath" value="/WEB-INF/freemarker"></property>
    </bean>

maven依赖
<dependency>
          <!--在spring中使用Freemarker这个必不可少,否则报错-->
          <groupId>org.springframework</groupId>
          <artifactId>spring-context-support</artifactId>
          <version>4.3.6.RELEASE</version>
      </dependency>
      <dependency>
          <groupId>org.freemarker</groupId>
          <artifactId>freemarker</artifactId>
          <version>2.3.28</version>
      </dependency>

六  Spring MVC执行流程

6.1 SpringMVC整个流程图:

6.2 具体业务流程:

1.DispatcherServlet表示前置控制器,是整个SpringMVC的控制中心。用户发出请求,DispatcherServlet接收请求并拦截请求,

我们假设请求的url为:http://localhost:9999/SpringMVC/input-product

如上url拆分成三部分:

(1)http://localhost:9999 服务器域名

(2)SpringMVC部署在服务器(http://localhost:9999)上的web站点

(3)input-product表示控制器

通过分析,如上url表示为:请求位于服务器localhost:9999上的SpringMVC站点的input-product控制器

2.HandlerMapping为处理器映射。DispatcherServlet调用HandlerMapping,HandlerMapping根据请求url查找Handler

3.HandlerExecution表示具体的Handler,其主要作用是根据url查找控制器,如上url被查找控制器为:input-product

4.HandlerExecution将解析后的信息传递给DispatcherServlet,如解析控制器映射等

5.HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler

6.Handler让具体的Controller执行

7.Controller将具体的执行信息返回给HandlerAdapter,如ModelAndView

8.HandlerAdapter将视图逻辑名或模型传递给DispatcherServlet

9.DispatcherServlet调用视图解析器(ViewResolver)来解析HandlerAdapter传递的逻辑视图名

10.视图解析器将解析的逻辑视图名传给DispatcherServlet

11.DispatcherServlet根据视图解析器解析的视图结果,调用具体的视图

12.最终视图呈现给用户。

6.3 关键组件说明:

1)DispatcherServlet

DispatcherServlet 是前端控制器,从图 1 可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

2)HandlerMapping

HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

3)HandlerAdapter

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

4)Handler

Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

5)View Resolver

View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

6)View

View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。

七 Spring MVC @Controller和@RequestMapping

7.1、@Controller注解


在配置了注解的处理器映射器和适配器的情况下,当使用@Controller注解去标识一个类时,其实就是告诉SpringMVC该类时一个Handler控制器类。在配置<component-scan>标签后,当Spring初始化Bean信息时,会扫描到所有标注了@Controller注解的类,并作为Handler来加载。

【示例】创建用户控制器类(UserController.java),使用@Controller注解标识该类为一个控制器类。

/**
 * 用户控制器
 * 使用@Controller注解来标识它是一个控制器
 * @author pan_junbiao
 **/
@Controller
public class UserController
{
 
}

7.2、@RequestMapping注解


7.2.1 @RequestMapping注解的使用


@RequestMapping注解的作用是为控制器指定可以处理哪些URL请求,该注解可以放置在类上或者方法上。当放置在类上,提供初步的URL请求映射信息,即一个前置请求路径(相对于Web应用的根目录)。当放置在方法上时,提供进一步的细分URL映射信息,相对于类定义处的URL。若类定义处未标注@RequestMapping注解,则方法处标记的URL相对于Web应用的根目录。

【示例】创建用户控制器类(UserController.java),使用@Controllerg注解标识该类为一个控制器类,使用@RequestMapping注解为类和方法指定URL映射名。

/**
 * 用户控制器
 * 使用@Controller注解来标识它是一个控制器
 * 使用@RequestMapping注解为控制器类指定一个URL映射名
 * @author pan_junbiao
 **/

@Controller
@RequestMapping("/user")
public class UserController
{
    /**
     * 获取用户名称
     * 使用@RequestMapping注解为方法指定一个URL映射名
     */
    @RequestMapping("/getUserName")
    public String getUserName()
    {
        return "pan_junbiao的博客";
    }
}


假设工程名为“SpringMVCFirstDemo”,默认后缀名为“.action”。那么这里的getUserName()处理的URL请求路径则是:http://localhost:8080/SpringMVCFirstDemo/user/getUserName.action

7.2.2 @RequestMapping注解提供的属性


@RequestMapping注解提供了一些属性,可以用来限制请求方法、请求参数、请求头。

7.2.3 method属性


对于请求方法,@RequestMapping注解的method属性可以指定“GET” 或“POST”请求类型,表明该URL只能以某种请求方式请求才能获得相应:

@Controller
public class UserController
{
    @RequestMapping(value = "/getUserName",method = RequestMethod.GET)
    public String getUserName()
    {
        return "hello";
    }
}


这里访问“/getUserName”请求时,只能接受GET请求。可以看到,指定“GET” 或“POST”请求类型需要由RequestMethod枚举类来表示
对于请求参数,@RequestMapping注解的params属性可以指定某一种参数名类型,当请求数据中含有该名称的请求参数时,才能进行相应:

@Controller
public class UserController
{
    @RequestMapping(value = "/getUserName",params = "username")
    public String getUserName()
    {
        System.out.println("只接受包含username参数的请求");
        return "pan_junbiao的博客";
    }
}


该配置表示,当一个URL请求中不包含有名称为“username”的参数时,该方法就拒绝此次请求。

此外,Spring4.3之后引入了一系列@RequestMapping的变种。如下:

@GetMapping
@PostMapping
@PutMapping
@PatchMapping
@DeleteMapping

 7.2.4 headers属性


对于请求头,@RequestMapping注解的headers属性可以指定某一种请求头类型,当请求数据头的类型符合指定值时,才能进行响应:

@Controller
public class UserController
{
    @RequestMapping(value = "/getUserName",headers = "contentType:text/html;charset=UTF-8")
    public String getUserName()
    {
        System.out.println("只接受请求头中contentType为:text/html;charset=UTF-8的请求");
        return "pan_junbiao的博客";
    }
}

该配置表示,当一个请求头中的contentType为“text/html;charset=UTF-8”的参数时,该方法才会处理此次请求。

7.2.5 consumes属性


@RequestMapping注解的consumes属性表示处理请求的提交内容类型(Content-Type),例如“application/json,text/html”。

@Controller
public class UserController
{
    @RequestMapping(value="/addUser",method=RequestMethod.POST,consumes="application/json")
    public void addUser(UserModel userModel)
    {
        //忽略具体逻辑...
    }
}


该配置表示方法仅处理request的Content-Type为“application/json”类型的请求。

7.2.6 produces属性


@RequestMapping注解的produces属性表示返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型时才返回。

@Controller
public class UserController
{
    @RequestMapping(value="/getUser",method=RequestMethod.GET,produces="application/json")
    @ResponseBody
    public UserModel getUser(String userId)
    {
        //忽略具体逻辑...
    }
}


该配置表示方法仅处理request请求中Accept头中包含“application/json”的请求,同时暗示了返回的内容类型为“application/json”。

小贴士:指定consumes和produces可以规范请求的Content-Type内容类型。

【示例】通过设置@RequestMapping注解的produces属性,解决SpringMVC返回字符串中文乱码问题。

/**
 * SpringMVC返回字符串结果,并解决中文乱码问题
 *
 * @author pan_junbiao
 */
@RequestMapping(value = "/getMessage", produces = {"text/html;charset=UTF-8;", "application/json;"})
@ResponseBody
public String getMessage()
{
    return "您好,";

八 Spring MVCh获取参数的几种方式

8.1 普通方式-请求参数名和Controller方法的参数一致

后台代码示例:

@Controller
@RequestMapping("/param")
public class TestParamController {
    private static final Logger logger = LoggerFactory.getLogger(TestParamController.class);
    /**
     * 请求参数名和Controller方法的参数一致
     * @param name 用户名
     * @param pwd 密码
     * @return
     *
     */
    @RequestMapping(value = "/add", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public String addUser(String name, String pwd){
        logger.debug("name:" + name + ",pwd:" + pwd);
        return "name:" + name + ",pwd:" + pwd;
    }

}

8.2 对象方式-请求参数名和Controller方法中的对象的参数一致

后台代码示例:

@Controller
@RequestMapping("/param")
public class TestParamController {
    private static final Logger logger = LoggerFactory.getLogger(TestParamController.class);
    /**
     * 请求参数名和Controller方法的参数一致
     * @param user 用户信息
     * @return
     *
     */
    @RequestMapping(value = "/addByObject", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
    @ResponseBody
    public String addUserByObject(User user){
        logger.debug("name:" + user.getName() + ",pwd:" + user.getPwd());
        return "name:" + user.getName() + ",pwd:" + user.getPwd();
    }
}

8.3 自定义方法参数名-当请求参数名与方法参数名不一致时

后台代码示例:

/**
 * 自定义方法参数名-当请求参数名与方法参数名不一致时
 * @param u_name 用户名
 * @param u_pwd 密码
 * @return
 */
@RequestMapping(value = "/addByDifName", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String addUserByDifName(@RequestParam("name") String u_name, @RequestParam("pwd")String u_pwd){
    logger.debug("name:" + u_name + ",pwd:" + u_pwd);
    return "name:" + u_name + ",pwd:" + u_pwd;
}

8.4 HttpServletRequest方式

后台代码示例:

/**
 * 通过HttpServletRequest接收
 * @param request
 * @return
 */

@RequestMapping(value = "/addByHttpServletRequest", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})

@ResponseBody

public String addUserByHttpServletRequest(HttpServletRequest request){

    String name = request.getParameter("name");

    String pwd = request.getParameter("pwd");

    logger.debug("name:" + name + ",pwd:" + pwd);

    return "name:" + name + ",pwd:" + pwd;

}

8.5 @PathVariable获取路径中的参数接收

后台代码示例:

/**

 * 通过@PathVariable获取路径中的参数 
 * @param name 用户名
 * @param pwd 密码
 * @return
 */

@RequestMapping(value = "/add/{name}/{pwd}", method = RequestMethod.GET, produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String addUserByPathVariable(@PathVariable String name, @PathVariable String pwd){
    logger.debug("name:" + name + ",pwd:" + pwd);
    return "name:" + name + ",pwd:" + pwd;

}

8.6 @RequestBody-JSON方式接收

后台代码示例:

/**
 * RequestBody-JSON 对象方式
 * @param user
 * @return
 */

@RequestMapping(value = "/addByObjectJSON", produces = {"application/json;charset=UTF-8"})
@ResponseBody
public String addUserByObjectJSON(@RequestBody User user){
    logger.debug("name:" + user.getName() + ",pwd:" + user.getPwd());
    return "name:" + user.getName() + ",pwd:" + user.getPwd();
}

 九 Spring MVC重定向和转发

9.1 请求转发和重定向的区别

请求重定向 和 请求转发都是web开发中资源跳转的方式。
	请求转发是服务器内部的跳转
		地址栏不发生变化
		只有一个请求响应
		可以通过request域传递数据
	请求重定向是浏览器自动发起对跳转目标的请求
		地址栏会发生变化
		两次请求响应
		无法通过request域传递对象

9.2 SpringMVC中实现转发和重定向

在SpringMVC中仍然可以使用传统方式实现转发和重定向
	request.getRequestDispatcher("").forward(request, response);
	response.sendRedirect("");
在SpringMVC中也提供了快捷方法实现转发和重定向
	只要在返回视图时,使用如下方式指定即可:
	redirect:/xxx.action
	forward:/xxx.action

9.3 代码实现转发和重定向

/**
	 * 实现转发
	 * @throws Exception 
	 */
	@RequestMapping("/hello11.action")
	public String hello11(HttpServletRequest request) throws IOException, Exception{
		request.setAttribute("name", "zsf");
		return "forward:/hello.action";
	}
	
	/**
	 * 实现重定向
	 * @throws Exception 
	 */
	@RequestMapping("/hello12.action")
	public String hello12(HttpServletRequest request) throws IOException, Exception{
		request.setAttribute("name", "zsf");
		return "redirect:/hello.action";
	}

十 Spring MVC @Autowired和@Service注解

10.1 @Autowired

    作为一个Spring开发者对@Autowired注解必定是非常了解了, 顾名思义自动装配,应该是Spring会自动将我们标记为@Autowired的元素装配好,查看Autowired定义:

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD,
                         ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Autowired {
    boolean required() default true;
}

很明显这个注解可以用到构造器,变量域,方法,注解类型上。文档上这样描述:将一个构造器,变量域,setter方法,config方法标记为被Spring DI 工具自动装配。换句话说,我们视图从bean 工厂中获取一个bean时,Spring会自动为我们装配该bean中标记为@Autowired的元素,而无需我们手动完成。这些相信大家都明白,但问题是,Spring是怎样做到的?在Spring MVC中怎样做到的?什么时候进行的自动装配?下面我们就来探究下这些问题。

10.2 BeanPostProcessor

    在@Autowired的定义中有下面一段话:

Note that actual injection is performed through a BeanPostProcessor 
which in turn means that you cannot use @Autowired to inject references into BeanPostProcessor
or BeanFactoryPostProcessor types. 
Please consult the javadoc for the AutowiredAnnotationBeanPostProcessor class 
(which, by default, checks for the presence of this annotation).

   意思是:实际的注入装配动作是由BeanPostProcessor执行的,翻过来说你不能将@Autowired注解用于BeanPostProcessor或BeanFactoryPostProcessor类型上。请查看AutowiredAnnotationBeanPostProcessor文档(默认情况下,被用来检查@Autowired注解)。

    文档说的很清楚了,BeanPostProcessor来执行自动装配,并且默认情况下使用AutowiredAnnotationBeanPostProcessor实现类完成。那我们不妨看一下他们的定义:

public interface BeanPostProcessor {
    Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
    Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

   BeanPostProcessor就一个回调接口,定义了两个方法声明,一个是实例化前被调用,一个是实例化后被调用,没啥好看的,我们直接看看它的实现类AutowiredAnnotationBeanPostProcessor:

BeanPostProcessor implementation that autowires annotated fields, setter methods and arbitrary 
config methods. Such members to be injected are detected through a Java 5 annotation: 
by default, Spring's @Autowired and @Value annotations.Also supports JSR-330's @Inject 
annotation, if available, as a direct alternative to Spring's own @Autowired.

Note: A default AutowiredAnnotationBeanPostProcessor will be registered by 
the "context:annotation-config" and "context:component-scan" XML tags.

NOTE: Annotation injection will be performed before XML 
injection; thus the latter configuration will override the former for properties 
wired through both approaches.

   上面是AutowiredAnnotationBeanPostProcessor类的描述文档摘要,大致意思是:这是BeanPostProcessor接口的一个实现,用来自动装配注解的变量域,setter方法和任意的config方法。这些被注入的元素是通过检测Java 5的注解完成的:默认情况下是@Autowired和@Value注解。同样也支持JSR-330的@Inject注解。并且,<context:annotation-config/>和<context:component-scan/>XML标签可以默认注册AutowiredAnnotationBeanPostProcessor到bean工厂中。最后,注解注入会在XML注入之前执行;因此后面的配置会覆盖前面已经装配好的元素。

10.3 装配过程

整个装配的过程、我们举个简单的例子:

//==================================Controller
@Controller
public class SimpleController {
    @Autowired
    private SimpleService simpleService;
}
//==================================Service
@Service("simpleService")
public class SimpleServiceImpl implements SimpleService {
    @Autowired
    private SimpleDao simpleDao;
}
//===================================Repository
@Repository("simpleDao")
public class SimpleDaoImpl implements SimpleDao {
}

1)在某一时刻Spring调用了 Bean工厂 的 getBean(beanName) 方法。beanName可能是simpleController,或者simpleService,simpleDao,顺序没关系(因为后面会有依赖关系的处理)。我们假设simpleController吧。

2)getBean方法首先会调用Bean工厂中定义的getSingleton(beanName)方法,来判断是否存在该名字的bean单例,若果存在则返回,方法调用结束。

3)否则,Spring会检查是否存在父工厂,如果有则返回,方法调用结束。

4)否则,Spring 会检查该bean 定义(BeanDefinition实例,用来描述Bean结构,上篇文章讲到过,component-scan 扫描后,就是将beanDefinition实例放入Bean工厂,此时Bean还没有被实例化。)是否有依赖关系,如果有,执行1)步,获取依赖的bean实例。

5)否则,Spring会尝试创建这个bean实例,创建实例前,Spring会检查确定调用的构造器,并实例化该Bean。

6)实例化完成后,Spring会调用Bean工厂的populateBean方法来填充bean实例的属性,也就是我们前面提到的自动转配了。populateBean方法便是调用了BeanPostProcessor实例来完成属性元素的自动装配工作。

7)在元素装配过程中,Spring会检查被装配的属性是否存在自动装配的其他属性,然后递归调用getBean方法,直到所有@Autowired的元素都被装配完成。如在装配simpleController中的simpleService属性时,发现SimpleServiceImpl实例中存在@Autowired属性simpleDao,然后调用getBean(simpleDao)方法,同样会执行1)-7)整个过程。所以可以看成一个递归过程。

8)装配完成后,Bean工厂会将所有的bean实例都添加到工厂中来。

注:我们知道Spring MVC是多线程单实例的MVC框架,就是说,对于同一个Controller,只会生成一个实例来处理所有的请求,因此bean实例只会实例化一次,并被存放在工厂中,以供其他请求使用。

好了,大致了解整个流程后我们看一下Spring的具体行为吧。

10.4 Bean 工厂

我们可以看到DefaultLiableBeanFactory继承自DefaultSingletonBeanRegistry,AbstractBeanFactory,AbstractAutowireCapableBeanFactory。下面就列出了一下相关的Bean工厂中的属性和方法:

//==========================================================================================
//==============================DefaultListableBeanFactory================================
//==========================================================================================
//beanName-beanDefinition 的映射表
private final Map<String, BeanDefinition> beanDefinitionMap = 
                                            new ConcurrentHashMap<String, BeanDefinition>(64);
//beanName 列表
private final List<String> beanDefinitionNames = new ArrayList<String>();
//==========================================================================================
//=============================AbstractBeanFactory=================================
//==========================================================================================
//注册了所有的BeanPostProcessor实例,包括前面提到的用来处理@Autowired注解的
//AutowiredAnnotationBeanPostProcessor 
private final List<BeanPostProcessor> beanPostProcessors = new ArrayList<BeanPostProcessor>();
//可能存在的父亲Bean工厂
private BeanFactory parentBeanFactory;
//==========================================================================================
//==============================DefaultSingletonBeanRegistry================================
//==========================================================================================
//beanName--bean单例的映射表
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(64);
//注册过的beanName集合
private final Set<String> registeredSingletons = new LinkedHashSet<String>(64);
//beanName与该bean所依赖的Beans集合的映射表,如simpleDao依赖与simpleService,如果还有其他
service使用该Dao,如simpleService2那么是simpleDao->[simpleService,simpleService2]
private final Map<String, Set<String>> dependentBeanMap 
                                            = new ConcurrentHashMap<String, Set<String>>(64);
//恰好与上面相反,beanName与该bean所包含的beans的集合映射表,如simpleController->[simpleService]
private final Map<String, Set<String>> dependenciesForBeanMap 
                                            = new ConcurrentHashMap<String, Set<String>>(64);

   可以看到Bean工厂中即存有bean definition的映射表,也存有bean name的别表,以及bean实例的映射表,还有依赖关系图。理解了这个对下面的实例化以及装配过程会有很大帮助。

10.5 实例化与装配

  下面我们就从头到尾看一下整个的实例化和装配过程:

public Object getBean(String name) throws BeansException {
    return doGetBean(name, null, null, false);
}

@SuppressWarnings("unchecked")
protected <T> T doGetBean(final String name, final Class<T> requiredType, 
                          final Object[] args, boolean typeCheckOnly)throws BeansException {
        //去掉工厂bean的前缀或者将别名转化为规范名
        final String beanName = transformedBeanName(name);
        Object bean;
        // 检查是否有已经注册的bean实例
        Object sharedInstance = getSingleton(beanName);
        if (sharedInstance != null && args == null) {
            //如果是工厂bean,获取工厂bean创建的bean
            bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
        }
        else {
            //判断是否有父工厂,并且其中是否存在该bean实例?
            BeanFactory parentBeanFactory = getParentBeanFactory();
            if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
                String nameToLookup = originalBeanName(name);
                if (args != null) {
                    return (T) parentBeanFactory.getBean(nameToLookup, args);
                }
                else {
                    return parentBeanFactory.getBean(nameToLookup, requiredType);
                }
            }
            if (!typeCheckOnly) {
                markBeanAsCreated(beanName);//将该beanName标记为已经实例化
            }
            //获取该beanName对应的BeanDefinition实例,从上面说到的beanDefinitionMap表中查找
            final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
            checkMergedBeanDefinition(mbd, beanName, args);
            //判断是否有依赖bean
            String[] dependsOn = mbd.getDependsOn();
            if (dependsOn != null) {
                for (String dependsOnBean : dependsOn) {
                    getBean(dependsOnBean);//如果有依赖bean,递归调用getBean方法
                    registerDependentBean(dependsOnBean, beanName);//将依赖关系保存到 上面提到的
                    //dependBeanMap和dependencyForBeanMap中。
                }
            }
            // 真正的开始床架bean实例了。激动吧
            if (mbd.isSingleton()) {//beanDefinition中指定该实例为单例
                //去工厂中获取单例,如果没有创建一个,然后添加到工厂中,否则直接返回
                sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
                    public Object getObject() throws BeansException {
                        try {
                            return createBean(beanName, mbd, args);
                        }
                    }
                });
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
            else if (mbd.isPrototype()) {//原型方式,每次都创建一个新的实例
                Object prototypeInstance = null;
                try {
                    beforePrototypeCreation(beanName);
                    prototypeInstance = createBean(beanName, mbd, args);
                }
                finally {
                    afterPrototypeCreation(beanName);
                }
                bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            }
            else {//其他方式,如目前主体无关,不赘述
            }
        }
        return (T) bean;
    }

   这个方法好长,真的好长,我还删除了一些与当前主体无关的代码呢,好吧,不要被吓住,如果是自己写的代码,再长都很容易弄懂,类的继承关系再复杂都能轻车熟路,那是因为我们都清楚的知道每一行的用意,看别人的代码一样,多看几遍就明白了,再复杂都一样。

    首先Spring会检查beanName,获取规范的beanName,然后它会检查是否存在已经注册的单例(查询上面提到的singletonObjects映射表),如果有的话就直接返回了,一切就结束了,否则的话,会查看是否存在父工厂,如果有调用父工厂的getBean方法,如果没有呢?

    好吧那就要着手创建实例了,首先查看beanDefinitionMap查找该beanName对应的beanDefinition实例,然后根据该实例判断是否存在依赖关系,如果存在在递归的调用getBean方法,直到所有的依赖关系都正确的实例化和装配完成,并且将这些依赖关系保存到上面提到的dependencyForBeanMap 和dependentBeanMap中。

    接下来,Spring查看BeanDefinition来确定该Bean应该是单例方式创建还是原型方式创建?如果是单例的话,Spring会调用getSingleton方法查找或创建一个单例(下面会详聊),如果是原型的话,每次调用getBean方法都会创建一个新的实例,看上面代码便会一清二楚了。

那下面我们就看看这个getSingleton方法做了什么?

public Object getSingleton(String beanName, ObjectFactory singletonFactory) {
        //这不就是上面说的bean实例映射表吗?哈,被同步了,保证线程安全啊
        synchronized (this.singletonObjects) {
            Object singletonObject = this.singletonObjects.get(beanName);
            if (singletonObject == null) {//第一次创建当然是空
                beforeSingletonCreation(beanName);//这个是看看当前的beanName是否在排除列表中,如果是
                //则抛出异常
                boolean recordSuppressedExceptions = (this.suppressedExceptions == null);
                try {//这里调用了上面的匿名内部类的getObject方法了,实则调用了createBean方法
                    singletonObject = singletonFactory.getObject();
                }//这不,添加到了singleObjects映射表中了,以备下次使用
                addSingleton(beanName, singletonObject);
            }
            return (singletonObject != NULL_OBJECT ? singletonObject : null);
        }
    }

   这里稍微清晰了,查看singletonObjects映射表,看是否存在已经注册的单例,如果没有调用createBean方法创建一个,并且注册到singletonObjects映射表中,否则直接返回就Ok了。

下面就是createBean了, we are close。

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, 
                                                                    final Object[] args) {
        // Instantiate the bean.
        BeanWrapper instanceWrapper = null;
        if (mbd.isSingleton()) {
            instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
        }
        if (instanceWrapper == null) {//实例化动作
            instanceWrapper = createBeanInstance(beanName, mbd, args);
        }
        //.....some codes we don't care.
        //......
        // Initialize the bean instance.
        Object exposedObject = bean;
        try {/装配动作
            populateBean(beanName, mbd, instanceWrapper);
            if (exposedObject != null) {//后面会讲到,暂时不关心
                exposedObject = initializeBean(beanName, exposedObject, mbd);
            }
        }
        //........some codes we don't care.
        return exposedObject;
    }

   比较清晰(同样去除了一些我们不关心的代码),上面的方法分了我们期望的两部执行,第一实例化Bean,第二装配Bean。

    第一步实例化主要是通过确定调用的构造器来最终调用Class.newInstance(args)方法来实例化Bean。不做细究,有兴趣可以自己看看,比较简单,主要是第二部装配,也就是处理我们的@Autowired注解(终于找到正题了)。

10.6 执行装配

    方法populateBean执行了最终的Autowired动作,我们看一下它做了什么?话说这块有点麻烦了,开始之前想讲几个比较重要的类和接口吧:

    A) PropertyValue:这是一个用来表示Bean属性的对象,其中定义了属性的名字和值等信息,如simpleService,和simpleDao属性。

    B) PropertyDescriptor:这个事Bean属性的描述符,其中定义了该属性可能存在的setter和getter方法,以及所有Bean的Class对象。

    C) InjectionMetadata:这个是注入元数据,包含了目标Bean的Class对象,和注入元素(InjectionElement)集合.

    D) InjectionElement:这个是注入元素,包含了注入元素的java.lang.reflect.Member 的对象,以及一个PropertyDescriptor对象。就是对java.lang.reflect.Member的一个封装,用来执行最终的注入动作,它有两个子类,分别是:AutowiredFieldElement表示字段属性,AutowiredMethodElement表示方法。

    其实最终的目标就是将PropertyValue中的value值赋给InjectionElement中的Member对象。那它是怎么做的呢?

protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
        //尝试从BeanDefinition中获取PropertyValue的属性集合,很明显没有值呢还。
        PropertyValues pvs = mbd.getPropertyValues();
        //.....其中执行了一些BeanPostProcessor的postProcessAfterInstantiation动作,我们不关心。
        //.....移除了

        //这里比较重要,这里会设置上面的PropertyValues的值,默认情况下是getResolvedAutowiredMode方法返回
        //0, 但是我们可以在xml配置文件中设置<beans/>标签的default-autowire属性来改变它。
        if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME ||
                mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
            MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
            // Add property values based on autowire by name if applicable.
            if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_NAME) {
                autowireByName(beanName, mbd, bw, newPvs);
            }
            // Add property values based on autowire by type if applicable.
            if (mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_BY_TYPE) {
                autowireByType(beanName, mbd, bw, newPvs);
            }
            pvs = newPvs;
        }
        boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
        boolean needsDepCheck = (mbd.getDependencyCheck() != 
                                                RootBeanDefinition.DEPENDENCY_CHECK_NONE);
        if (hasInstAwareBpps || needsDepCheck) {
            PropertyDescriptor[] filteredPds = 
                            filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
            if (hasInstAwareBpps) {
                //这里便是最最最重要的了,也就是最终的Autowired了。
                for (BeanPostProcessor bp : getBeanPostProcessors()) {
                    if (bp instanceof InstantiationAwareBeanPostProcessor) {
                        InstantiationAwareBeanPostProcessor ibp = 
                                                    (InstantiationAwareBeanPostProcessor) bp;
                        pvs = ibp.postProcessPropertyValues(//瞅到没,这个方法哦~~~
                                        pvs, filteredPds, bw.getWrappedInstance(), beanName);
                        if (pvs == null) {
                            return;
                        }
                    }
                }
            }
            if (needsDepCheck) {
                checkDependencies(beanName, mbd, filteredPds, pvs);
            }
        }
        applyPropertyValues(beanName, mbd, bw, pvs);
    }

   Spring 尝试获取bean definition的PropertyValue集合,开始当然是空的,然后下面便是进行根据名字或者类型为我们的PropertyValue集合进行赋值了, 在不设置<beans default-autowire="byName/byType"/>的情况下是不会调用这个方法的,如果设置了byName,我们来看看做了什么?

protected void autowireByName(
            String beanName, AbstractBeanDefinition mbd, 
            BeanWrapper bw, MutablePropertyValues pvs) {
        //找到还没赋值的属性名称,看下面方法
        String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
        for (String propertyName : propertyNames) {
            if (containsBean(propertyName)) {
                //递归调用getBean,如果没有创建并注册,有了直接返回。
                Object bean = getBean(propertyName);
                //将刚得到或创建的bean赋值给PropertyValue
                pvs.add(propertyName, bean);
                //并将该属性名和实例注册到依赖关系映射表dependentBeanMap和dependencyForBeanMap中
                registerDependentBean(propertyName, beanName);
            }
        }
    }

protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
        Set<String> result = new TreeSet<String>();
        PropertyValues pvs = mbd.getPropertyValues();
        PropertyDescriptor[] pds = bw.getPropertyDescriptors();
        //遍历bean的所有属性,并将符合条件的属性名添加到结果列表中
        for (PropertyDescriptor pd : pds) {
            if (pd.getWriteMethod() != null 
                && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
                    !BeanUtils.isSimpleProperty(pd.getPropertyType())) {
                result.add(pd.getName());
            }
        }
        return StringUtils.toStringArray(result);
    }

   上面两段代码的意思是,查看当前bean的所有属性(描述符),然后依次判断查找符合条件的属性,并添加到属性名称数组中,然后遍历这个数组,对其中的属性名依次调用getBean(propertyName)方法来获取或创建该名称的bean实例,并将该bean实例设为PropertyValue的value值,最后添加到依赖关系映射表中(dependencyForBeanMap和dependentBeanMap)。好了此时PropertyValues有值了,后面就可以用它来注入到bean的属性中了。我们接着看上面populateBean方法。

    PropertyValue值设置后,Spring会调用getBeanPostProcessor方法遍历Bean工厂中注册的所有BeanPostProcessor,其中就包括AutowiredAnnotationBeanPostProcessor(这些BeanPostProcessor都是系统默认硬编码注册到bean工厂中的)。接着就会调用AutowiredAnnotationBeanPostProcessor的postProcessPropertyValues方法,并将之前的PropertyValues和bean实例传递进去。

//虽然PropertyValues属性传递过去了,但是并没有使用它直接赋值给属性变量(还不清楚为什么会传递它,其实没用到)
@Override
public PropertyValues postProcessPropertyValues(PropertyValues pvs, 
                PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        //调用下面的方法获取InjectionMetadata对象(其实InjectionElement集合)
        InjectionMetadata metadata = findAutowiringMetadata(bean.getClass());
        try {
            metadata.inject(bean, beanName, pvs);
        }
        return pvs;
    }

private InjectionMetadata findAutowiringMetadata(Class<?> clazz) {
        // 先找缓存
        InjectionMetadata metadata = this.injectionMetadataCache.get(clazz);
        if (metadata == null) {
            synchronized (this.injectionMetadataCache) {
                metadata = this.injectionMetadataCache.get(clazz);
                if (metadata == null) {
                    //缓存没有,调用buildAutowiringMetadata方法构建
                    metadata = buildAutowiringMetadata(clazz);
                    this.injectionMetadataCache.put(clazz, metadata);
                }
            }
        }
        return metadata;
    }

private InjectionMetadata buildAutowiringMetadata(Class<?> clazz) {
        LinkedList<InjectionMetadata.InjectedElement> elements = 
                                        new LinkedList<InjectionMetadata.InjectedElement>();
        Class<?> targetClass = clazz;
        do {//这里一个循环,因为要考虑父类的字段和方法
            LinkedList<InjectionMetadata.InjectedElement> currElements = 
                                    new LinkedList<InjectionMetadata.InjectedElement>();
            for (Field field : targetClass.getDeclaredFields()) {
                //遍历每一个field,找到被标记为@Autowired的field
                Annotation annotation = findAutowiredAnnotation(field);
                if (annotation != null) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        continue;//不可一世static的。
                    }
                    boolean required = determineRequiredStatus(annotation);
                    //创建AutowiredFieldElement。
                    currElements.add(new AutowiredFieldElement(field, required));
                }
            }
            for (Method method : targetClass.getDeclaredMethods()) {
                //遍历所有方法,这里有个桥方法的处理,我们不关心
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                Annotation annotation = BridgeMethodResolver
                                    .isVisibilityBridgeMethodPair(method, bridgedMethod) ?
                                        findAutowiredAnnotation(bridgedMethod) :
                                     findAutowiredAnnotation(method);
                if (annotation != null && 
                            method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                    if (Modifier.isStatic(method.getModifiers())) {
                        continue;
                    }
                    if (method.getParameterTypes().length == 0) {
                    }
                    boolean required = determineRequiredStatus(annotation);
                    PropertyDescriptor pd = BeanUtils.findPropertyForMethod(method);
                    //创建AutowiredMethodElement。
                    currElements.add(new AutowiredMethodElement(method, required, pd));
                }
            }
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        }
        while (targetClass != null && targetClass != Object.class);
        //将InjectionElement集合添加到新建的InjectionMetadata中。
        return new InjectionMetadata(clazz, elements);
    }

   上面三个方法看似复杂其实很简单,首先Spring尝试调用findAutowiringMetadata方法获取该bean的InjectionMetadata实例(也就是有哪些属性需要被自动装配,也就是查找被@Autowired注解标记的元素)。怎么获取呢?首先去缓存里面找,找不到就遍历bean的和父类的字段域和方法,如果别标记为@Autowired并且不是静态的就添加到InjectionMetadata中,并添加到缓存中(各种缓存啊)。获得InjectionMetadata对象后便遍历其中的所有InjectionElement对象,调用其中的inject方法。前面说了InjectionElement有两个实现类,我们只看一个就可以,因为基本相同:

@Override
        protected void inject(Object bean, String beanName, PropertyValues pvs) 
                                                            throws Throwable {
            Field field = (Field) this.member;
            try {
                Object value;
                if (this.cached) {
                    value = resolvedCachedArgument(beanName, this.cachedFieldValue);
                }
                else {
                    DependencyDescriptor descriptor 
                                        = new DependencyDescriptor(field, this.required);
                    Set<String> autowiredBeanNames = new LinkedHashSet<String>(1);
                    TypeConverter typeConverter = beanFactory.getTypeConverter();
                    //这里是重中之重,获取真正的属性值。
                    value = beanFactory.resolveDependency(descriptor, beanName, 
                                                autowiredBeanNames, typeConverter);
                }
                if (value != null) {
                    ReflectionUtils.makeAccessible(field);
                    field.set(bean, value);//最终赋值结束。
                }
            }
        }
    }

  可以看到,虽然PropertyValues属性传递过去了,但是并没有使用它直接赋值给属性变量(还不清楚为什么会传递它,其实没用到),而是通过调用bean工厂的resolveDependency方法来获取属性值得。那我们看一下resolveDependency做了什么?

protected Object doResolveDependency(DependencyDescriptor descriptor, 
                                        Class<?> type, String beanName,
                                     Set<String> autowiredBeanNames, 
                                TypeConverter typeConverter) throws BeansException  {
        if (type.isArray()) {//如果属性类型是数组
            Class<?> componentType = type.getComponentType();
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType, descriptor);
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            return converter.convertIfNecessary(matchingBeans.values(), type);
        }//如果属性是集合,并且是接口
        else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
            Class<?> elementType = descriptor.getCollectionType();
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType, descriptor);
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
            return converter.convertIfNecessary(matchingBeans.values(), type);
        }//如果属性是Map并且是接口
        else if (Map.class.isAssignableFrom(type) && type.isInterface()) {
            Class<?> keyType = descriptor.getMapKeyType();
            Class<?> valueType = descriptor.getMapValueType();
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, valueType, descriptor);
            if (autowiredBeanNames != null) {
                autowiredBeanNames.addAll(matchingBeans.keySet());
            }
            return matchingBeans;
        }//自定义类型了
        else {//都调用了这个方法
            Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
            if (matchingBeans.size() > 1) {
                String primaryBeanName = determinePrimaryCandidate(matchingBeans, descriptor);
                if (autowiredBeanNames != null) {
                    autowiredBeanNames.add(primaryBeanName);
                }
                return matchingBeans.get(primaryBeanName);
            }
            // We have exactly one match.
            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
            if (autowiredBeanNames != null) {
                autowiredBeanNames.add(entry.getKey());
            }
            return entry.getValue();
        }
    }

   这个方法其实就是根据类型到bean工厂中查找类型匹配的bean实例,然后就看到了这几个条件分支语句,如果是数组,集合,映射表,自定义类型都执行了差不多的操作,findAutowireCandidate方法。这个方法会去工厂中执行类型匹配的查找,将匹配的结果集返回,不同的是,集合数组类型会通过TypeConverter进行结果的转换。

    到此为止,找到了属性的匹配值,然后反射赋值就完成了整个的自动装配过程。可以看出,@Autowired是通过类型来进行自动装配的。

    上面是属性的赋值过程也就是InjectionFieldElement的inject方法,InjectionMethodElement的inject方法大致相同只是对每一个方法参数执行一次resolveDependency方法来获取参数值,然后反射执行方法。

    到此为止,整个实例化和装配过程也就讲完了,我们总结一下:

1)一切都是从bean工厂的getBean方法开始的,一旦该方法调用总会返回一个bean实例,无论当前是否存在,不存在就实例化一个并装配,否则直接返回。

2)实例化和装配过程中会多次递归调用getBean方法来解决类之间的依赖。

3)Spring几乎考虑了所有可能性,所以方法特别复杂但完整有条理。

4)@Autowired最终是根据类型来查找和装配元素的,但是我们设置了<beans default-autowire="byName"/>后会影响最终的类型匹配查找。因为在前面有根据BeanDefinition的autowire类型设置PropertyValue值得一步,其中会有新实例的创建和注册。就是那个autowireByName方法。

10.7 结束

    我们上面讲完了整个Autowire过程了。那么,还有一个问题,上一篇我们知道了什么时候执行的配置文件读取和组件扫描,但Spring MVC是在什么时候开始执行真个实例化过程的呢?很简单就在组件扫描完成之后,bean工厂的refresh方法中(还记得吗?)

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();
            //前面说过,这里面执行了,组件扫描和配置文件读取
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                //这里注册了BeanPostProcessor,包括AutowiredAnnotationBeanPostProcessor
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                //这里就执行了所有非延迟加载的实例化工作。//here
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
        }
    }

   就是上面的finishBeanFactoryInitialization方法执行了装配工作,该方法会调用bean工厂的preInstantiateSingletons方法,这个方法会遍历所有注册的bean definition实例,如果是单例并且是非延迟加载的就调用getBean方法。

参考文章和目录:

https://my.oschina.net/HeliosFly/blog/203902

http://c.biancheng.net/spring_mvc/

https://www.cnblogs.com/xiaoxi/p/5695783.html

今天就分享到这儿吧,小伙伴们点赞、收藏、评论,下期见~~





这篇关于Java SpringMVC 万字长文基础知识总结《上》-王者笔记建议收藏的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程