Spring学习笔记(2)一DI依赖注入和Spring Bean配置、注解原理、动态注入

本文深入探讨了Spring框架中的IoC(控制反转)和依赖注入(DI)概念,详细介绍了Spring容器如何管理和配置对象,包括SpringBean的配置、容器类型、生命周期,以及Spring配置文件的不同方式(XML、注解、Java)。此外,文章还涵盖了动态注册bean和Spring注解配置的优势与使用方法。

一、IOC和依赖注入DI


      Spring容器是Spring框架的核心。容器将创建对象,它们连接在一起,配置它们,并从创建到销毁管理他们的整个生命周期。在Spring容器使用依赖注入(DI)来管理组成应用程序的组件。这些对象被称为Spring Beans。 

        IOC(Inversion of Control):传统的方法,当某个java对象A需要调用对象B时,是由调用者(对象A)通过new关键字来创建对象B的,而在Spring中,则是由spring完成创建的,所以“控制反转”了。Spring通过一种称作控制反转(IoC)的技术促进了低耦合。当应用了IoC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创建或者查找依赖对象。你可以认为IoC与JNDI(JNDI是 Java 命名与目录接口Java Naming and Directory Interface))相反——不是对象从容器中查找依赖,而是容器在对象初始化时不等对象请求就主动将依赖传递给它。
       DI(Dependency Injection)—IOC的另一种表述方式:即组件以一些预先定义好的方式(例如: setter方法)接受来自如容器的资源注入.相对于 IOC而言,这种表述更直接。

       注入可以理解为是对一个对象进行初始化,也就是省去new的这个步骤,类似于工厂模式一样,通过一个工厂制造出这个对象,如果遇到修改,只需要改一处就行了。实现spring注入一般有两种方法,配置文件,或者用注解。各有不同的作用。

       容器获得其上的哪些对象进行实例化,配置和组装通过阅读提供的配置元数据的说明。配置元数据可以通过XML,Java注释或Java代码来表示。下面的图是Spring如何工作的高层次图。 Spring IoC容器是利用Java的POJO类和配置元数据的产生完全配置和可执行的系统或应用程序。

        

二、Spring容器


Spring提供了以下两种不同类型的容器。

1、Spring BeanFactory 容器:

      这是最简单的容器DI提供基本的支持和定义由org.springframework.beans.factory.BeanFactory 接口. BeanFactory或者相关的接口,例如实现BeanFactoryAware,InitializingBean,DisposableBean,仍然存在在Spring向后兼容性与大量的与Spring整合第三方框架的目的。

2、Spring ApplicationContext 容器

        此容器添加了更多的企业特定的功能,例如从一个属性文件解析文本消息的能力,并发布应用程序事件感兴趣的事件监听器的能力。此容器是由 org.springframework.context.ApplicationContext 接口定义.

         在ApplicationContext 容器包括BeanFactory的容器的所有功能,所以因此通常建议在BeanFactory。 BeanFactory仍然可以用于重量轻的应用,如移动装置或基于小应用程序的应用中的数据量和速度是显著。

      实例化一个spring容器,容器会自动预初始化所有Bean实例。

ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //实例化一个Spring容器,
Boss boss = (Boss) ac.getBean("boss"); //取得对象boss

三、Spring Bean配置


bean定义包含所需要的容器要知道以下称为配置元数据的信息:

1、如何创建一个bean
2、Bean 生命周期的详细信息
3、Bean 依赖关系

上述所有配置元数据转换成一组的下列属性构成每个bean的定义。

属性描述
class此属性是强制性的,并指定bean类被用来创建bean。
name此属性指定唯一bean标识符。在基于XML的配置元数据时,您可以使用id和/或name属性来指定bean标识符
scope该属性指定一个特定的bean定义创建,它会在bean作用域本章要讨论的对象范围。
constructor-arg这是用来注入的依赖关系,并在接下来的章节中进行讨论。
properties这是用来注入的依赖关系,并在接下来的章节中进行讨论。
autowiring mode这是用来注入的依赖关系,并在接下来的章节中进行讨论。
lazy-initialization mode延迟初始化的bean告诉IoC容器创建bean实例时,它首先要求,而不是在启动时。
initialization method回调只是在bean的所有必要属性后调用已设置的容器。它会在bean的生命周期章节中讨论。
destruction method当包含该bean容器被销毁所使用的回调。它会在bean的生命周期章节中讨论。

Spring IoC容器完全由在此配置元数据实际写入的格式解耦。有下列提供的配置元数据的Spring容器三个重要的方法:

  1.     基于XML的配置文件。
  2.     基于注解的配置
  3.     基于Java的配置

bean创建的几个重要步骤:
1) 创建bean实例,也就是bean的实体化,创建过程不仅仅只有java的反射机制,还结合了动态代理的方式
2) 记录创建bean的objectFactory
3) 属性注入
4) 初始化bean
5) 注册disposablebean

Spring bean的生命周期:

四、Spring bean的XML配置


     Spring配置文件是一个或多个标准的XML文档,applicationContext.xml是Spring的默认配置文件,当容器启动时找不到指定的配置文档时,将会尝试加载这个默认的配置文件。

      下面列举的是一份比较完整的配置文件模板,文档中各XML标签节点的基本用途也给出了详细的解释

1、import导入

在我们的beans.xml中就可以引入和使用entity.xml文件中的bean配置(代码中config/spring为我们新建的包config.spring)

<import resource="config/spring/entity.xml"/>

Bean的作用域
scope指bean的作用域,在配置bean时,有scope属性来配置bean的作用
注意:在整合struts和spring时,需要将action设为scope="prototype";

 <bean id="addr" class="cn.sxt.vo.Address" scope="singleton">
    <!-- scope:bean的作用域 (默认是singleton):
    singleton:单例(用计数器记录),整个容器中只有一个对象的实例;
    prototype原型:每次获取bean都产生一个新的对象;
    request:每次请求时,创建一个新的对象;
    session:在回话的范围内是一个对象(servlet的session);
    global session: 只在portlet下有用,表示是application
    application:在应用范围内,只有一个对象;
    -->

2.设置注入:

 属性必须写setter方法。格式为:
  要求被注入的属性必须有set方法。set方法的方法名由set+属性(属性首字母大写),如果属性是boolean类型,没有get方法(是is);

public class Student {
    private String name;
    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println("name="+name);

    }
}
 <bean id="student" class="cn.sxt.vo.Student">
        <property name="name" value="hgs"></property>
    </bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml");
        Student stu=(Student)ac.getBean("student");
        stu.show();
    }

}

2.构造注入:必须写有参的构造函数。


<bean id="car" class="wang.spring.Car ">
  <constructor-arg  index=" 0" type=“int” value=" 001” />
  <constructor-arg  index=" 1" type=“String” value=" baoma” />
</bean>

利用上面代码,可以新建一个实例car,并给它的属性赋值。

3.依赖注入:当引用了其他类的对象时。

<property name=" " ref=" ">或者<constructor-arg ref=" ">

public class Address {
    private String address;
    public String getAddress() {
        return address;

    }

    public void setAddress(String address) {
        this.address = address;

    }
}

Student类中引用Address类的对象(Student的实例变量是另外一个类Address的对象):

private Address addr;
    public void setAddr(Address addr) {
        this.addr = addr;
}

beans.xml:

 <bean id="addr" class="cn.sxt.vo.Address">
        <property name="address" value="北京海淀"/>
    </bean>
    <bean id="student" class="cn.sxt.vo.Student">
        <property name="name" value="hgs"></property>
        <property name="addr" ref="addr"></property>
    </bean>

4.集合的注入:

public class Student {
    private String name;
    public void setName(String name) {
        this.name = name;
    }

    private String[] books;
    public void setBooks(String[] books) {// 数组注入
        this.books = books;
    }

    private List<String> hobbies;
    public void setHobbies(List<String> hobbies) {//List注入
        this.hobbies = hobbies;
    }

    private Map<String, String> cards;
    public void setCards(Map<String, String> cards) {//map注入
        this.cards = cards;
    }
    private Set<String> games;
    public void setGames(Set<String> games) { //set注入
        this.games = games;
    }

    private Properties info;
    public void setInfo(Properties info) {//属性注入
        this.info = info;
    }
}

beans.xml做以下配置

<bean id="student" class="cn.sxt.vo.Student">
        <property name="name" value="张三丰"></property>
        <property name="addr" ref="addr"></property>
        <property name="books">
            <array>
                <value>傲慢与偏见</value>
                <value>仲夏夜之梦</value>
                <value>雾都孤儿</value>
            </array>
        </property>
        <property name="hobbies">
            <list>
                <value>羽毛球</value>
                <value>乒乓球</value>
                <value>玻璃球</value>
                <value>排球</value>
            </list>
        </property>
        <property name="cards">
            <map>
                <entry key="中国银行" value="1545615345415"></entry>
                <entry>
                    <key><value>农业银行</value></key>
                    <value>54654861231543</value>
                </entry>
            </map>
        </property>
        <property name="games">
            <set>
                <value>LOL</value>
                <value>dota</value>
                <value>cs</value>
                <value>dnf</value>
                <value>cf</value>
                
            </set>
        </property>
        <property name="wife"><null/></property>
        <property name="info">
        <props>
            <prop key="学号">2015534001</prop>
            <prop key="sex">男</prop>
            <prop key="name">张三</prop>
        </props>
        </property>
    </bean>

5.自动注入(spring4):

是用来简化spring的配置文件,在配置bean时,可以配置bean的autowire属性,用于指定注入类型

<bean id="userDao" class="cn.sxt.dao.impl.UserDaoMySqlImpl"></bean>
    <!-- autowire:自动装配,用于简化spring的配置
         no     不使用自动装配
         byname 根据名称(set方法名)来查找相应的bean,如果有则装配上去
           使用形式:xml头文件最后面加上default-autowire="byName"
         byType 根据类型进行装配,不同去管bean的id(bean的id可以写任意)
                 但是同一种类型的bean只能有一个(尽量慎用byType)
         constructor 当使用构造器实例化bean时,适用byType的方式装配构造方法
     -->
    <bean id="service" class="cn.sxt.service.impl.UserServiceImpl" autowire="constructor"></bean>

可以配置全局的自动装配类型,在xml文件的头部:default-autowire="byname"

【注意】推荐不使用自动装配,而使用annotation

五、Spring bean注解配置


注解配置相对于 XML 配置具有很多的优势:

  • 它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——因为这些信息都可以通过 Java 反射机制获取。
  • 注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,如果配置信息和 Java 代码放在一起,有助于增强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,往往需要在程序文件和配置文件中不停切换,这种思维上的不连贯会降低开发效率。

因此在很多情况下,注释配置比 XML 配置更受欢迎,注释配置有进一步流行的趋势。

1、从Spring 3.x开始,Spring被外界最为诟病的一点就是配置繁多,号称“配置地狱”,各种xml文件,出了问题非常难排查。

2、从Spring 4.x开始,Spring.io提供了三种方式编织Bean:
            1)利用注解:隐式配置,例如:@Autowired、@Bean、@Component等,通过注解来简化xml文件。
            2)利用Java文件:显示配置,比xml配置的优势是具备类型安全
            3)利用传统的xml配置文件

3、在Spring Boot中几乎可以完全弃用xml配置文件。

注解可以分为两大类。JSR-250规范注解方式 和 Spring自带的注解方式。

通过注解的方式装配时,必须在配置文件中添加一个bean,它其实是一个注解处理器,用于解析注解。

 JSR-250规范注解方式的处理器:

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>

Spring自带的注解方式的处理器:

<bean class="org.sprinframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/>

当然,我们有更方便的方法,两种注解方式都可以用下面的方式隐式得注册注解处理器:

  <context:annotation-config>

 Spring提供如下几个Annotation来标注Spring Bean:

@Component标注一个普通的Spring Bean;
@Controller:标注一个控制器组件类;
@Service:标注一个业务逻辑组件类;
@Repository:标注一个Dao组件;

这时,bean.xml文件中就不必用<bean>来定义bean了。

1、注解配置bean的创建

我们先了解@Controller

Spring容器框架包org.springframework.stereotype下Controller注解接口源代码如下

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.stereotype;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
    String value() default "";
}

@Target({java.lang.annotation.ElementType.Type})表是注解使用在类上;
@Retention(RetentionPolicy.RUNTIME)表示注解在程序运行状态下还有效;
@Component表示该类对象以默认单例模式存放在spring容器框架中;
@Documented无关重要,略;

其他的注解@Respository,@Service等,这两个注解的源代码和Controller的源代码一样,除了接口名,都共有一个value抽象函数。

这些所有的注解都会被component注解接口注解,所有“继承”了component注解接口的注解修饰用户的类会被spring中的注解处理器获取(getAnonations()),判定存在component注解后,注解处理器会在spring容器框架中根据用户类的全限定名通过java的反射机制创建这个用户类的对象,并放到spring容器框架中进行管理。

2、如何找到这些bean的类文件

说完bean的创建过程,那spring是如何找到这些bean的类文件的呢?

我们在spring的配置文件中,添加一行<context:component-scan base-package="com.spring.demo"/>用来指定这些类所在的包。标签context:component-scan的属性base-package中设置要扫描的包。spring框架中肯定有根据base-package属性扫描得到所有需要管理的bean对象,这个节点中的所有属性会被放入扫描模块对象工具中去,结果就是将所有的bean对象放到spring的容器中去。
 

1)扫描类文件:根据配置利用asm技术扫描.class文件,并将包含@Component及元注解为@Component的注解@Controller、@Service、@Repository   或者还支持Java EE 6的@link javax.annotation.ManagedBean和jsr - 330的 @link javax.inject.Named,如果可用。的bean注册到beanFactory中

2)是处理属性或方法中的注解
        注册@Configuration处理器ConfigurationClassPostProcessor,
        注册@Autowired、@Value、@Inject处理器AutowiredAnnotationBeanPostProcessor,
        注册@Required处理器RequiredAnnotationBeanPostProcessor、在支持JSR-250条件下注册javax.annotation包下注解处理器CommonAnnotationBeanPostProcessor,包括@PostConstruct、@PreDestroy、@Resource注解等、支持jpa的条件下,注册org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor处理器,处理jpa相关注解注册@EventListener处理器EventListenerMethodProcessor
         注解处理器的的实例化和处理器的注册时同步的,实例化后放入到beanFactory的beanProcessors列表中去。

Spring框架的核心就是IOC,通过controller一类注解的bean的实例化过程。

可以大体总结spring注解的工作原理:

1)利用asm技术扫描class文件,转化成Springbean结构,把符合扫描规则的(主要是是否有相关的注解标注,例如  @Component)bean注册到Spring 容器中beanFactory
2)注册处理器,包括注解处理器
4)实例化处理器(包括注解处理器),并将其注册到容器的beanPostProcessors列表中
5)创建bean的过程中,属性注入或者初始化bean时会调用对应的注解处理器进行处理。

SpringBoot项目的Bean装配默认规则是根据Application类(SpringBoot项目入口类)所在的包位置从上往下扫描!这个类的位置很关键:例如Application类所在的包为:com.tl. projec.app,则只会扫描com.tl. projec.app包及其所有子包,如果service或dao所在包不在com.tl. projec.app及其子包下,则不会被扫描!

解决方案:

         1.将SpringBoot项目的启动类,放在所以包之上。如Application类放在:“com.tl. project”包下,其他类放在“com.tl. projec.bean”、“com.tl. projec.controller”、“com.tl. projec.dao”等。

        2.使用@ComponentScan注解,在springboot的启动类上注解配置,需要扫描的包,例如:

         @ComponentScan(basePackages={" com.tl. projec.app"," com.tl. projec.bean "})

注意:经测试,依赖注入,不能向static属性注入Spring上下文中的对象。
 

3、bean注入方式

       我们可以自由地使用任何标准的Spring框架技术去定义beans和它们注入的依赖。 简单起见, 我们经常使用 @ComponentScan 注解搜索beans, 并结合 @Autowired 构造器注入。
       如果使用上面建议的结构组织代码( 将应用类放到根包下) , 你可以添加 @ComponentScan 注解而不需要任何参数。 你的所有应用程序组件( @Component , @Service , @Repository , @Controller 等) 将被自动注册为Spring Beans。

1 )、构建器注入

下面是一个 @Service Bean的示例, 它使用构建器注入获取一个需要的 UserDao bean。

package com.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class DatabaseAccountService implements AccountService {
	private final UserDao userDao;
	@Autowired
	public DatabaseAccountService(UserDao userDao) {
		this.userDao = userDao;
	} 
	// ...
}

注:注意如何使用构建器注入来允许 userDao 字段被标记为 final , 这意味着 userDao 后续是不能改变的。

2)、直接通过@Autowried 注解注入

直接通过@Autowried 注解对Service对象进行注解即可

在Controller中:
@Controller
@RequestMapping("/test")
public class ExampleController {
     @Autowired  
     private ExampleService service;       
}

在Service中
@Component 
public class ExampleServiceImpl Implements ExampleService {
     @Autowired  
     private ExampleDao exampleDao;       
}

六、常用Spring注解


常用注解(annotations)列表
@ResponseBody  :用该注解修饰的函数,会将结果直接填充到HTTP的响应体中,一般用于构建RESTful的api;
@Controller :用于定义控制器类,在spring 项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层)。
@RestController  :@ResponseBody和@Controller的合集
@RequestMapping :提供路由信息,负责URL到Controller中的具体函数的映射。
@EnableAutoConfiguration :Spring Boot自动配置(auto-configuration):尝试根据你添加的jar依赖自动配置你的Spring应用。例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么我们将自动配置一个内存型(in-memory)数据库”。你可以将@EnableAutoConfiguration或者@SpringBootApplication注解添加到一个@Configuration类上来选择自动配置。如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。例子代码如下:
@ComponentScan :表示将该类自动发现(扫描)并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。
@Configuration :相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。
@SpringBootApplication :相当于@EnableAutoConfiguration、@ComponentScan和@Configuration的合集。
@Import   :用来导入其他配置类。
@ImportResource :用来加载xml配置文件。
@Autowired :自动注入,自动导入依赖的bean。自动从spring的上下文找到合适的bean来注入
@Service :一般用于修饰service层的组件
@Repository :使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。

@Qualifier 或者 @Resource : @Autowired 和 @Qualifier 或者 @Resource 配合使用,指定bean的名称,这样spring就知道你的接口要调用那个实现类了。

上面的Autowired和Resource是用来修饰字段,构造函数,或者设置方法,并做注入的。而Service,Controller,Repository,Component则是用来修饰类,标记这些类要生成bean。

具体说明:

1:@Component
    @Component是所有受Spring 管理组件的通用形式,@Component注解可以放在类的头上,@Component不推荐使用。

2:@Controller
    @Controller对应表现层的Bean,也就是Action,例如:

@Controller  
@Scope("prototype")  
public class UserController {  
  
} 

 注:实际上,使用@component,也可以起到@Controller同样的作用。

       使用@Controller注解标识UserController之后,就表示要把UserController交给Spring容器管理,在Spring容器中会存在一个名字为"UserController"的action,这个名字是根据UserController类名来取的。注意:如果@Controller不指定其value【@Controller】,则默认的bean名字为这个类的类名首字母小写,如果指定value【@Controller(value="UserController")】或者【@Controller("UserController")】,则使用value作为bean的名字。
      这里的UserController还使用了@Scope注解,@Scope("prototype")表示将Action的范围声明为原型,可以利用容器的scope="prototype"来保证每一个请求有一个单独的Action来处理,避免struts中Action的线程安全问题。spring 默认scope是单例模式(scope="singleton"),这样只会创建一个Action对象,每次访问都是同一Action对象,数据不安全,struts2 是要求每次次访问都对应不同的Action,scope="prototype" 可以保证当有请求的时候都创建一个Action对象。

3:@RequestMapping

    @RequestMapping是一种通过匹配URL路径来访问相应页面的,@RequestMapping 注解将类似 “/user”这样的URL映射到整个类或特定的处理方法上。一般来说,类级别的注解映射特定的请求路径到表单控制器上,而方法级别的注解只是映射为一个特定的HTTP方法请求(“GET”,“POST”等)或HTTP请求参数。

@Controller  
@RequestMapping("/user")  
public class UserController {  
    @RequestMapping(value = "/list", method = {RequestMethod.GET,RequestMethod.POST})  
	public String list(HttpServletRequest request) {  
  
	}  
}  

4:@Autowired

    将 @Autowired 注释标注在成员变量上 ,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。

    @Autowired 根据bean 类型从spring 上线文中进行查找,注册类型必须唯一,否则报异常。与@Resource 的区别在于,@Resource 允许通过bean 名称或bean 类型两种方式进行查找@Autowired(required=false) 表示,如果spring 上下文中没有找到该类型的bean 时, 才会使用new SoftPMServiceImpl();      @Autowired 标注作用于 Map 类型时,如果 Map 的 key 为 String 类型,则 Spring 会将容器中所有类型符合 Map 的 value 对应的类型的 Bean 增加进来,用 Bean 的 id 或 name 作为 Map 的 key。      @Autowired 还有一个作用就是,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。

@Controller  
@RequestMapping("/user")  
public class UserController {  
    @Autowired  
    private UserBiz userBiz;  
}  

5:@RequestParam

    @RequestParam将请求的参数绑定到方法中的参数上。其实,即使不配置该参数,注解也会默认使用该参数。如果想自定义指定参数的话,如果将@RequestParam的 required 属性设置为false(如@RequestParam(value="id",required=false))。

6:@RequestBody

    @RequestBody是指方法参数应该被绑定到HTTP请求Body上。

@RequestMapping(value = "/user",method = RequestMethod.GET)  
public void UserInfo(@RequestBody String userId,User user){  
      
}  

7:@ResponseBody

    @ResponseBody与@RequestBody类似,它的作用是将返回类型直接输入到HTTP response body中。最常用的我们使用ajax传输json,需要再类上面配置@ResponseBody。

@ResponseBody  
@RequestMapping(value = "/user", method = RequestMethod.POST)  
public String list() {      
    return "Hello World";  
}  

8:@ModelAttribute

    @ModelAttribute可以作用在方法或方法参数上,当它作用在方法上时,标明该方法的目的是添加一个或多个模型属性(model attributes)。该方法支持与@RequestMapping一样的参数类型,但并不能直接映射成请求。控制器中的@ModelAttribute方法会在@RequestMapping方法调用之前而调用,示例如下:

@ModelAttribute  
public User addUser(@RequestParam String userId) {  
    ...  
}  

    @ModelAttribute方法用来在model中填充属性,如填充下拉列表、宠物类型或检索一个命令对象比如账户(用来在HTML表单上呈现数据)。
    @ModelAttribute方法有两种风格:一种是添加隐形属性并返回它。另一种是该方法接受一个模型并添加任意数量的模型属性。用户可以根据自己的需要选择对应的风格。

    @ModelAttribute作用在方法参数上

    当@ModelAttribute作用在方法参数上时,表明该参数可以在方法模型中检索到。如果该参数不在当前模型中,该参数先被实例化然后添加到模型中。一旦模型中有了该参数,该参数的字段应该填充所有请求参数匹配的名称中。这是Spring MVC中重要的数据绑定机制,它省去了单独解析每个表单字段的时间。
    @ModelAttribute是一种很常见的从数据库中检索属性的方法,它通过@SessionAttributes使用request请求存储。在一些情况下,可以很方便的通过URI模板变量和类型转换器检索属性。

9:@Cacheable 和@CacheFlush 

    @Cacheable :声明一个方法的返回值应该被缓 存  
    例如:@Cacheable(modelId = "testCaching")  
    @CacheFlush :声明一个方法是清空缓存的触发器                   
    例如:@CacheFlush(modelId = "testCaching") 

10:@Resource  

    @Resource 默认按bean 的name 进行查找,如果没有找到会按type 进行查找, 此时与@Autowired 类似在没有为 @Resource 注解显式指定 name 属性的前提下,如果将其标注在 BeanFactory 类型、ApplicationContext 类型、ResourceLoader 类型、 ApplicationEventPublisher 类型、MessageSource 类型上,那么 Spring 会自动注入这些实现类的实例,不需要额外的操作。此时 name 属性不需要指定 ( 或者指定为""),否则注入失败。

11:@PostConstruct 和@PreDestroy  

    @PostConstruct 在方法上加上注解@PostConstruct ,这个方法就会在Bean 初始化之后被Spring 容器执行  (注:Bean 初始化包括,实例化Bean ,并装配Bean 的属性(依赖注入))。

    @PreDestroy 在方法上加上注解@PreDestroy ,这个方法就会在Bean 被销毁前被Spring 容器执行。

12:@Repository 

    与@Controller 、@Service 类似,都是向spring 上下文中注册bean。

13:@SessionAttributes 

    Spring 允许我们有选择地指定 ModelMap 中的哪些属性需要转存到 session 中, 以便下一个请求属对应的 ModelMap 的属性列表中还能访问到这些属性。 这一功能是通过类定义处标注 @SessionAttributes 注解来实现的。 @SessionAttributes 只能声明在类上,而不能声明在方法上。    

例如   
@SessionAttributes("User") // 将ModelMap 中属性名为User的属性 

@SessionAttributes({"attr1","attr2"}) 

@SessionAttributes(types = User.class)   

@SessionAttributes(types = {User.class,Dept.class})   

@SessionAttributes(types = {User.class,Dept.class},value={"attr1","attr2"}) 

14:@InitBinder

    如果希望某个属性编辑器仅作用于特定的 Controller ,可以在 Controller 中定义一个标注 @InitBinder 注解的方法, 可以在该方法中向 Controller 了注册若干个属性编辑器 

@InitBinder    
public void initBinder(WebDataBinder binder) {    
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); dateFormat.setLenient(false);    
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));    
}  

15:@Required

    @ required 负责检查一个bean在初始化时其声明的 set方法是否被执行, 当某个被标注了 @Required 的 Setter 方法没有被调用,则 Spring 在解析的时候会抛出异常,以提醒开发者对相应属性进行设置。 @Required 注解只能标注在 Setter 方法之上。因为依赖注入的本质是检查 Setter 方法是否被调用了,而不是真的去检查属性是否赋值了以及赋了什么样的值。如果将该注解标注在非 setXxxx() 类型的方法则被忽略。

16:@Qualifier

    @Autowired                     @Qualifier("softService")

    private ISoftPMService softPMService; 

    使用@Autowired 时,如果找到多个同一类型的bean,则会抛异常,此时可以使用 @Qualifier("beanName"),明确指定bean的名称进行注入,此时与 @Resource指定name属性作用相同。

17:@PathVariable

    @PathVariable是用来获得请求url中的动态参数的,当使用@RequestMapping URI template 样式映射时, 即 someUrl/{paramId}, 这时的paramId可通过 @Pathvariable注解绑定它传过来的值到方法的参数上。

@RequestMapping(value="/user/{userId}",method = RequestMethod.GET)    
public String getUser(@PathVariable("userId") String userId){     
      return "";    
}  

七、动态注入


为什么需要动态注册bean

大部分时候,静态的配置信息即可满足系统需求。但是某些场景下,我们需要根据静态配置中的信息动态生成bean,此时就需要动态注册bean的功能。

如:

用户定义一个如下的接口,而接口的实现则由框架生成,不需要用户自行编写,此时实现类就需要动态注册到容器中。

@Rest("http://localhost:8081/test")
public interface IRequestDemo {

	@GET
	ResultBean get1();

	@GET("/get2")
	ResultBean getWithKey(@Param("key") String key);

	@GET("/get3")
	ResultBean getWithMultKey(@Param("key1") String key,     
	     @Param("key2") String key2);
	
}

@componet
public class test {
  @Autowired
  IRequestDemo demo;

  public String test() {
	String msg = "<h1>invoke remote rest result</h1>";
	ResultBean get1 = demo.get1();
	msg += "<br/>get1 result=" + get1;
	ResultBean get2 = demo.getWithKey("key-------");
	msg += "<br/>get2 result=" + get2;
	ResultBean get3 = demo.getWithMultKey("key11111", "key22222");
	msg += "<br/>get3 result=" + get3;
	return msg;
  }
}

动态注册bean的api

Spring中的bean定义都保存在 **BeanDefinitionRegistry** 接口中,单例的bean的实例都保存在 **SingletonBeanRegistry** 接口中。

因此动态注册bean也分为了两种方式:

1. 使用BeanDefinitionRegistry接口的void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException 方法

2. 使用SingletonBeanRegistry接口的void registerSingleton(String beanName, Object singletonObject) 方法

两者区别在于使用前者时,Spring容器会根据BeanDefinition实例化bean实例,而使用后者时,bean实例就是传递给registerSingleton方法的对象。

DefaultListableBeanFactory接口同时实现了这两个接口,在实践中通常会使用这个接口。

在普通bean中进行动态注册

可以在任何获得了BeanDefinitionRegistry或者SingletonBeanRegistry实例的地方进行动态注册。

但是如果bean不是在BeanFactoryPostProcessor中被注册,那么该bean则无法被**BeanPostProcessor**处理,即无法对其应用aop、Bean Validation等功能。

在**BeanFactoryPostProcessor**中进行动态注册

在Spring容器的启动过程中,BeanFactory载入bean的定义后会立刻执行BeanFactoryPostProcessor,此时动态注册bean,则可以保证动态注册的bean被BeanPostProcessor处理,并且可以保证其的实例化和初始化总是先于依赖它的bean。

例子

在BeanFactoryPostProcessor注册

@Component
@Slf4j
public class PersonBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        DefaultListableBeanFactory defaultListableBeanFactory
                = (DefaultListableBeanFactory) beanFactory;

        //注册Bean定义,容器根据定义返回bean
        log.info("register personManager1>>>>>>>>>>>>>>>>");
        BeanDefinitionBuilder beanDefinitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(PersonManager.class);
        beanDefinitionBuilder.addPropertyReference("personDao", "personDao");
        BeanDefinition personManagerBeanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        defaultListableBeanFactory.registerBeanDefinition("personManager1", personManagerBeanDefinition);

        //注册bean实例
        log.info("register personManager2>>>>>>>>>>>>>>>>");
        PersonDao personDao = beanFactory.getBean(PersonDao.class);
        PersonManager personManager = new PersonManager();
        personManager.setPersonDao(personDao);
        beanFactory.registerSingleton("personManager2", personManager);

    }
}

在普通bean中注册

@RestController
@Slf4j
public class PersonManagerRegisterController {

    /**
     * The Application context.
     */
    @Autowired
    GenericApplicationContext applicationContext;

    /**
     * The Bean factory.
     */
    @Autowired
    ConfigurableBeanFactory beanFactory;

    /**
     * 动态注册bean,此处注册的bean没有AOP的支持
     * curl http://localhost:8080/registerPersonManager
     */
    @GetMapping("/registerPersonManager")
    public void registerPersonManager() {
        PersonDao personDao = applicationContext.getBean(PersonDao.class);
        PersonManager personManager = new PersonManager();
        personManager.setPersonDao(personDao);
        beanFactory.registerSingleton("personManager3", personManager);

    }
... ...

八、Spring iocSpring IOC实例化各种非懒加载单例bean


1、注解容器类入口是AnnotationConfigApplicationContext

通过注解方式创建SpringBean,入口是AnnotationConfigApplicationContext类。例如:
ApplicationContext applicationContext = new  AnnotationConfigApplicationContext(MyConfig.class);

这个构造方法AnnotationConfigApplicationContext中执行了三步:
调用this()方法:

 ■ 完成ioc容器DefaultListableBeanFactory实例化;
 ■ 完成AnnotationBeanDefinitionReader bean解析器实例化;
 ■ 完成ClassPathBeanDefinitionScanner实例化(不知道这个是做啥的)

调用register(componentClasses)方法:

 ■ 完成将当前传入的配置解析成beanDefinition注册到容器中。

2、服务启动refresh()方法创建Bean


服务启动调用refresh()方法(Spring核心方法)刷新spring容器。关于refresh方法在启动流程的哪一步,具体可以看看:Spring Boot(6) 原理和启动流程_hguisu的博客-CSDN博客

refresh()方法是SpringIOC启动流程和Bean创建的核心方法。

refreshContext(context):刷新spring容器,装配context beanfactory等非常重要的核心组件。

  public void refresh() throws BeansException, IllegalStateException {
        Object var1 = this.startupShutdownMonitor;
        synchronized(this.startupShutdownMonitor) {
            this.prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
            this.prepareBeanFactory(beanFactory);

            try {
                this.postProcessBeanFactory(beanFactory);
                this.invokeBeanFactoryPostProcessors(beanFactory);
                this.registerBeanPostProcessors(beanFactory);
                this.initMessageSource();
                this.initApplicationEventMulticaster();
                this.onRefresh();
                this.registerListeners();
                this.finishBeanFactoryInitialization(beanFactory);
                this.finishRefresh();
            } catch (BeansException var9) {
                if(this.logger.isWarnEnabled()) {
                    this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
                }

                this.destroyBeans();
                this.cancelRefresh(var9);
                throw var9;
            } finally {
                this.resetCommonCaches();
            }

        }
    }


  ■ 调用 invokeBeanFactoryPostProcessors(beanFactory)方法:作用是将所有bean解析成beanDefinition
  ■ 调用 finishBeanFactoryInitialization(beanFactory)方法:完成所有非懒加载、单例bean的实例化。finishBeanFactoryInitialization是Bean创建的核心方法

3、finishBeanFactoryInitialization是Bean创建的核心方法

Bean创建入口是AbstractApplicationContext中的finishBeanFactoryInitialization(beanFactory);

finishBeanFactoryInitialization具体实现了什么?

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
 
    // Initialize conversion service for this context.
    // 一、为上下文初始化类型转换器
    if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
 
        beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
 
        beanFactory.setConversionService(
        beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
 
    }
 
    // 二、检查上下文中是否存在类型转换器
    if (!beanFactory.hasEmbeddedValueResolver()) {
 
        beanFactory.addEmbeddedValueResolver(strVal ->         getEnvironment().resolvePlaceholders(strVal));
 
    }
 
    // 三、尽早初始化LoadTimeWeaverAware bean,以便尽早注册它们的转换器。
    // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
    String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
 
    for (String weaverAwareName : weaverAwareNames) {
 
        getBean(weaverAwareName);
 
    }
 
    // Stop using the temporary ClassLoader for type matching.
    // 四、禁止使用临时类加载器进行类型匹配
    beanFactory.setTempClassLoader(null);
 
    // Allow for caching all bean definition metadata, not expecting further changes
    // 五、允许缓存所有的bean的定义数据
    beanFactory.freezeConfiguration();
 
    // Instantiate all remaining (non-lazy-init) singletons.
    // 六、准备实例化bean
    beanFactory.preInstantiateSingletons();
 
}


 ■ 最终主要是调用了 beanFactory.preInstantiateSingletons()方法:完成所有非懒加载、单例bean的实例化功能。


4、beanFactory.preInstantiateSingletons()主要逻辑

beanFactory.preInstantiateSingletons()中主要逻辑如下:
  ■ 获取当前ioc容器中所有单例bean的beanName;
  ■ 遍历所有beanName,获取每一个beanName对应合并后的BeanDefinition对象;
  ■ 如果是FactoryBean对象则直接走FactoryBean的实例化逻辑,否则直接走bean的实例化逻辑,即直接调用getBean(beanName)方法。
  ■ 调用所有bean后初始化逻辑。

@Override
public void preInstantiateSingletons() throws BeansException {
    // ...
    // 获取容器中所有Bean的beanName
    List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);
    
    // 触发所有非懒加载单例beans的初始化工作
    // Trigger initialization of all non-lazy singleton beans...
    for (String beanName : beanNames) {
       //获取一个合并的BeanDefinition
       RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
       if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
           //判断是否是FactoryBean对象
           if (isFactoryBean(beanName)) {
               //如果是FactoryBean对象,通过&+beanName获取FactoryBean
               Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
               if (bean instanceof FactoryBean) {
                   FactoryBean<?> factory = (FactoryBean<?>) bean;
                   boolean isEagerInit;
                   if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                       isEagerInit = AccessController.doPrivileged(
                           (PrivilegedAction<Boolean>) ((SmartFactoryBean<?>) factory)::isEagerInit,
                           getAccessControlContext());
                   }
                   else {
                       isEagerInit = (factory instanceof SmartFactoryBean &&
                                      ((SmartFactoryBean<?>) factory).isEagerInit());
                   }
                   if (isEagerInit) {
                       getBean(beanName);
                   }
               }
           }
           else {
               //调用初始化Bean方法:因为我们没有添加FactoryBean类型的对象, 一般都会进入这个getBean
               getBean(beanName);
           }
       }
    }
    // 调用所有Bean后初始化回调
    // Trigger post-initialization callback for all applicable beans...
    for (String beanName : beanNames) {
        Object singletonInstance = getSingleton(beanName);
        if (singletonInstance instanceof SmartInitializingSingleton) {
            SmartInitializingSingleton smartSingleton = (SmartInitializingSingleton) singletonInstance;
            if (System.getSecurityManager() != null) {
                AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
                    smartSingleton.afterSingletonsInstantiated();
                    return null;
                }, getAccessControlContext());
            }
            else {
                smartSingleton.afterSingletonsInstantiated();
            }
        }
    }
}

5、getBean(beanName);方法

getBean(beanName);方法获取Bean,直接调用doGetBean()。当前的getBean(beanName)它是有返回值的,一会当我们往下跟进的是时候会发现会存在递归的现象,这一点巧妙的实现了@Autowired处理setter方式实现循环引用。

简化流程:排除了各种包装和检查的过程:

  1. 流程从 getBean 方法开始,getBean 是个空壳方法,所有逻辑直接到 doGetBean 方法中。
  2. transformedBeanName 将 name 转换为真正的 beanName(name 可能是 FactoryBean 以 & 字符开头或者有别名的情况,所以需要转化下)。
  3. 然后通过 getSingleton(beanName) 方法尝试从缓存中查找是不是有该实例 sharedInstance(单例在 Spring 的同一容器只会被创建一次,后续再获取 bean,就直接从缓存获取即可)。
  4. 如果有的话,sharedInstance 可能是完全实例化好的 bean,也可能是一个原始的 bean,所以再经 getObjectForBeanInstance 处理即可返回。
  5. 当然 sharedInstance 也可能是 null,这时候就会执行创建 bean 的逻辑,将结果返回。

 

spring中创建bean具体执行最终是在abstractbeanfactory这个类的doGetBean方法创建bean

6、doGetBean()方法:getBean()方法会继续调用doGetBean()方法

1、主要逻辑调用getSingleton(beanName)方法,先从缓存中获取bean,如果缓存中存在当前bean则直接返回bean,否则往下执行。

2、通过String[] dependsOn = mbd.getDependsOn()判断当前bean是否存在依赖,如果存在依赖则先实例化依赖。

doGetBean()方法它主要做了如下几件事:
1)首先将传递进来的name转换成了beanName
原因1: FactoryBean的实现类的存储方式和其他的类完全相同,添加上&是获取不到的, 因此我们将&去掉 原因2: 解决别名的问题
2)为什么在创建bean之前先调用getSingleton()?
现在是Spring启动的过程中,是在准备实例化bean,为什么一开始就来getSingleton(),跟进源码查看这个方法,它最终实现中有一行代码是这样的Object singletonObject = this.singletonObjects.get(beanName);而这个singletonObjects就是微观层面的IOC容器,循环创建刚开始时,IOC确实是空的,但是我前面存在剧透,一开始的getBean()方法是存在递归调用现象的,直接举2个例子: 第一:假如现在在实例化A,结果有发现需要给A注入B, 那Spring是不是得获得B,怎么获得呢? 递归使用getBean(BName)完成, 第二个例子: A被添加上了@Lazy注解,是懒加载的,但是终究有一个会通过getBean(AName)获取A,这是发现A是实例化需要B,B肯定已经实例化完事了,同样是通过递归getBean(BName)实现注入, 在这两个过程中就是getSingleton()保证不会重复创建已经存在的实例。

我们关注的重点其实是第二个getSingleton(beanName()->{xxx})
在第二个getSingleton()方法中才是真正的去实例化bean的方法。
最后,在当前的方法最后将bean返回了
前面我就是说过了,getBean(beanName)存在递归调用的情况,为什么我会一直说这个事呢,因为如果不知道这个事的话,这些代码看起来是没有头绪的,但是明白这个事,看代码就变得很有逻辑,我在简单总结一下怎个玩这个递归呢? 假设现在通过getBean(AName)来注入A对象,但是呢发现了A依赖B对象,于是在getBean(AName)里面调用getBean(BName),通过这个方法返回出B对象完成A的注入。
源码删除了很多:

protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
 
@Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
    final String beanName = transformedBeanName(name);
    Object bean;

    Object sharedInstance = getSingleton(beanName);
    if (sharedInstance != null && args == null) {
        // 如果存在的话,将其取出赋值给bean,后续直接返回这个bean
        bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // Fail if we're already creating this bean instance:
        // We're assumably within a circular reference.
        // 来到这里就说明要获取的bean还没有实例化过
        try {
            一大推逻辑。。。。。。。。
 
            // Create bean instance.
            if (mbd.isSingleton()) {
                // 实例化bean
                sharedInstance = getSingleton(beanName, () -> { 
                    // 真正的完成bean的创建
                    return createBean(beanName, mbd, args);
                });
 
                bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            }
            
          
        } catch (BeansException ex) {
            cleanupAfterBeanCreationFailure(beanName);
            throw ex;
        }
    }
   
    return (T) bean;
}

主要逻辑如下:
■ 调用getSingleton(beanName)方法,先从缓存中获取bean,如果缓存中存在当前bean则直接返回bean,否则往下执行。
 

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 先从singletonObjects中获取(一级缓存)
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 在从earlySingletonObjects中获取(二级缓存)
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // Consistent creation of early reference within full singleton lock
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 最后从singletonFactories中获取(三级缓存)
                        ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            //
                            singletonObject = singletonFactory.getObject();
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

7、Spring是如何解决循环依赖的问题?

getSingleton用到缓存,这个就是 Spring 为了解决单例的循环依赖问题而设计的三级缓存:

    private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
    private final Set<String> registeredSingletons = new LinkedHashSet(256);
    private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
    private final Set<String> inCreationCheckExclusions = Collections.newSetFromMap(new ConcurrentHashMap(16));

这三级缓存的作用分别是:

singletonObjects(一级缓存):完成初始化的单例对象的 cache,这里的 bean 经历过 实例化->属性填充->初始化 以及各种后置处理。

earlySingletonObjects(二级缓存)存放原始的 bean 对象(完成实例化但是尚未填充属性和初始化),仅仅能作为指针提前曝光,被其他 bean 所引用,用于解决循环依赖的 。singletonFactories(三级缓存):在 bean 实例化完之后,属性填充以及初始化之前,如果允许提前曝光,Spring 会将实例化后的 bean 提前曝光,也就是把该 bean 转换成 beanFactory 并加入到 singletonFactories。

如果缓存没有的话,我们就要创建了,接着我们以单例对象为例,再看下创建 bean 的逻辑(大括号表示内部类调用方法):

1、创建 bean 从以下代码开始,一个匿名内部类方法参数(总觉得 Lambda 的方式可读性不如内部类好理解)。

if (mbd.isSingleton()) {sharedInstance = getSingleton(beanName, () -> {try {return createBean(beanName, mbd, args); }catch (BeansException ex) { destroySingleton(beanName);throw ex; } }); bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);}

getSingleton() 方法内部主要有两个方法:

public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory){// 创建 singletonObjectsingletonObject = singletonFactory.getObject();// 将 singletonObject 放入缓存 addSingleton(beanName, singletonObject);}

2、getObject() 匿名内部类的实现真正调用的又是 createBean(beanName, mbd, args)。

3、往里走,主要的实现逻辑在 doCreateBean方法,先通过 createBeanInstance 创建一个原始 bean 对象。

4、接着 addSingletonFactory 添加 bean 工厂对象到 singletonFactories 缓存(三级缓存)。

5、通过 populateBean 方法向原始 bean 对象中填充属性,并解析依赖,假设这时候创建 A 之后填充属性时发现依赖 B,然后创建依赖对象 B 的时候又发现依赖 A,还是同样的流程,又去 getBean(A),这个时候三级缓存已经有了 beanA 的“半成品”,这时就可以把 A 对象的原始引用注入 B 对象(并将其移动到二级缓存)来解决循环依赖问题。这时候 getObject() 方法就算执行结束了,返回完全实例化的 bean。

6、最后调用 addSingleton 把完全实例化好的 bean 对象放入 singletonObjects 缓存(一级缓存)中,打完收工。

Spring 创建 bean 主要分为两个步骤,创建原始 bean 对象,接着去填充对象属性和初始化。每次创建 bean 之前,我们都会从缓存中查下有没有该 bean,因为是单例,只能有一个。当我们创建 beanA 的原始对象后,并把它放到三级缓存中,接下来就该填充对象属性了,这时候发现依赖了 beanB,接着就又去创建 beanB,同样的流程,创建完 beanB 填充属性时又发现它依赖了 beanA,又是同样的流程,不同的是,这时候可以在三级缓存中查到刚放进去的原始对象 beanA,所以不需要继续创建,用它注入 beanB,完成 beanB 的创建。既然 beanB 创建好了,所以 beanA 就可以完成填充属性的步骤了,接着执行剩下的逻辑,闭环完成。这就是单例模式下 Spring 解决循环依赖的流程了。

总结:

Spring 是如何解决的循环依赖?

Spring 为了解决单例的循环依赖问题,使用了三级缓存。其中一级缓存为单例池(singletonObjects),二级缓存为提前曝光对象(earlySingletonObjects),三级缓存为提前曝光对象工厂(singletonFactories)。

假设A、B循环引用,实例化 A 的时候就将其放入三级缓存中,接着填充属性的时候,发现依赖了 B,同样的流程也是实例化后放入三级缓存,接着去填充属性时又发现自己依赖 A,这时候从缓存中查找到早期暴露的 A,没有 AOP 代理的话,直接将 A 的原始对象注入 B,完成 B 的初始化后,进行属性填充和初始化,这时候 B 完成后,就去完成剩下的 A 的步骤,如果有 AOP 代理,就进行 AOP 处理获取代理后的对象 A,注入 B,走剩下的流程。

为什么要使用三级缓存呢?二级缓存能解决循环依赖吗?

如果没有 AOP 代理,二级缓存可以解决问题,但是有 AOP 代理的情况下,只用二级缓存就意味着所有 Bean 在实例化后就要完成 AOP 代理,这样违背了 Spring 设计的原则,Spring 在设计之初就是通过 AnnotationAwareAspectJAutoProxyCreator 这个后置处理器来在 Bean 生命周期的最后一步来完成 AOP 代理,而不是在实例化后就立马进行 AOP 代理。


■ 通过String[] dependsOn = mbd.getDependsOn()判断当前bean是否存在依赖,如果存在依赖则先实例化依赖。

// 实例化当前Bean所有的依赖
// Guarantee initialization of beans that the current bean depends on.
String[] dependsOn = mbd.getDependsOn();
if (dependsOn != null) {
    for (String dep : dependsOn) {
        if (isDependent(beanName, dep)) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                            "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
        }
        registerDependentBean(dep, beanName);
        try {
            getBean(dep);
        }
        catch (NoSuchBeanDefinitionException ex) {
            throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                            "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
        }
    }
}


■ 调用createBean(beanName, mbd, args)方法开始进行实例化bean。
最后调用AbstractAutowireCapableBeanFactory.doCreateBean()方法,逻辑如下:

8、AbstractAutowireCapableBeanFactory.doCreateBean()


■ 执行InstantiationAwareBeanPostProcessor后置处理器方法postProcessBeforeInstantiation(beanClass, beanName)。(Aop的核心功能就是通过这个后置处理器完成的,AnnotationAwareAspectJAutoProxyCreator.postProcessBeforeInstantiation()方法,核心逻辑后面具体描述)。SpringAOP实现原理?
■ 调用doCreateBean(beanName, mbdToUse, args)方法完成bean实例化。

 

@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
  throws BeanCreationException {
  // ...省略一些不重要的方法
  try {
      // 执行InstantiationAwareBeanPostProcessor初始化后置处理器方法。这里是处理AOP的主要后置处理器。
      // Aop的核心后置处理器AnnotationAwareAspectJAutoProxyCreator就是实现了InstantiationAwareBeanPostProcessor接口。后面会专门介绍AOP的执行流程。
  // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
      Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
      if (bean != null) {
          return bean;
      }
  }
  catch (Throwable ex) {
      throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName,
                                      "BeanPostProcessor before instantiation of bean failed", ex);
  }

  try {
      //开始实例化Bean
      Object beanInstance = doCreateBean(beanName, mbdToUse, args);
      if (logger.isTraceEnabled()) {
          logger.trace("Finished creating instance of bean '" + beanName + "'");
      }
      return beanInstance;
  }
  catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
      // A previously detected exception with proper bean creation context already,
      // or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
      throw ex;
  }
  catch (Throwable ex) {
      throw new BeanCreationException(
          mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex);
  }
}


实例化Bean的核心方法doCreateBean()方法主要实现逻辑如下:
■ 调用createBeanInstance()方法实例化bean。Spring实例化构造器推断原理?
■ 通过determineConstructorsFromBeanPostProcessors(beanClass, beanName)推断实例化使用的构造器。这里只有AutowiredAnnotationBeanPostProcessor后置处理器实现这个方法。那么这个方法主要告诉我们内容总结如下:
  ● 首先我们要知道一个bean如果有多个构造器可以通过@Autowired注解告诉容器我们需要通过哪个构造器来进行实例化。
  ● 如果存在两个以上@Autowired标注的构造函数,则IOC容器会报错。
  ● 最后调用一个@Autowired注解是required=false,那么会加入到候选列表中,如果此时存在无参构造函数也会加入到候选列表中,如果只有一个@AutoWired,则返回这个构造。
  ● 如果构造方法大于1个,但是都没有@AutoWired,那么返回null。
■ 最后调用ContructorResolver.instantiate()方法,通过反射机制进行实例化bean。注意此时实例化的bean是一个半成品,还没有进行属性注入。
■ 调用applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName)执行MergedBeanDefinitionPostProcessor后置处理器,这个后置处理器主要用于bean属性注入的前置工作,将所有类中包含@Autowired、@Resource等注解的属性和方法都放进RejectionMetadata中,用于后面进行自动装配。@Autowired、@Inject等依赖注入注解的实现原理?
■ 调用addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean))方法。
■ 这里通过三级缓存this.singletonFactories.put(beanName, singletonFactory)来解决循环依赖问题。
■ 调用populateBean()方法完成bean的属性填充。
■ 这里主要是通过AutowiredAnnotationBeanPostProcessor.postProcessProperties方法进行属性注入。主要逻辑是获取InjectionMetadata,然后遍历每个属性进行属性注入。
■ 调用initializeBean()方法完成bean的初始化。
■ 调用invokeAwareMethods(beanName, bean)回调方法,用来执行实现了BeanNameAware、BeanFactoryAware、BeanClassLoaderAware等setXXX()方法。
■ 通过applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName)方法,来调用BeanPostProcessor后置处理器前方法
■ 通过invokeInitMethods(beanName, wrappedBean, mbd)方法,调用bean初始化方法;
  ● 先调用实现了InitializingBean接口的afterPropertiesSet()方法;
  ● 在调用自定义的init-method中的方法。
■ 通过applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName)方法调用BeanPostProcessor后置处理器方法。

 

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
            throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        // 创建Bean实例
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    if (beanType != NullBean.class) {
        mbd.resolvedTargetType = beanType;
    }

    // Allow post-processors to modify the merged bean definition.
    synchronized (mbd.postProcessingLock) {
        if (!mbd.postProcessed) {
            try {
                // 调用MergedBeanDefinitionPostProcessor后置处理器,完成对AutoWiredAnnotationBeanPostProcessor后置处理器对自动装配功能的实现。
                applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
            }
            catch (Throwable ex) {
                throw new BeanCreationException(mbd.getResourceDescription(), beanName,
                                                "Post-processing of merged bean definition failed", ex);
            }
            mbd.postProcessed = true;
        }
    }

    // Eagerly cache singletons to be able to resolve circular references
    // even when triggered by lifecycle interfaces like BeanFactoryAware.
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
                                      isSingletonCurrentlyInCreation(beanName));
    if (earlySingletonExposure) {
        if (logger.isTraceEnabled()) {
            logger.trace("Eagerly caching bean '" + beanName +
                         "' to allow for resolving potential circular references");
        }
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }

    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // 属性填充
        populateBean(beanName, mbd, instanceWrapper);
        // 初始化Bean
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    catch (Throwable ex) {
        if (ex instanceof BeanCreationException && beanName.equals(((BeanCreationException) ex).getBeanName())) {
            throw (BeanCreationException) ex;
        }
        else {
            throw new BeanCreationException(
                mbd.getResourceDescription(), beanName, "Initialization of bean failed", ex);
        }
    }
    
    // ...省略一些不重要的方法

    // Register bean as disposable.
    try {
        registerDisposableBeanIfNecessary(beanName, bean, mbd);
    }
    catch (BeanDefinitionValidationException ex) {
        throw new BeanCreationException(
            mbd.getResourceDescription(), beanName, "Invalid destruction signature", ex);
    }

    return exposedObject;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hguisu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值