Spring系列--IOC详解

目录

  • IOC
    • 概念
    • 优点
    • 底层原理
      • 工厂模式
      • XML配置文件
    • IOC接口
    • IOC操作
      • 基于XML方式
        • 其它类型属性注入(xml)
        • 集合类属性注入
    • IOC容器初始化
    • FactoryBean
    • Bean生命周期
      • 实例化
      • 初始化
      • 使用中
      • 销毁
    • Bean的作用域
    • xml自动装配
    • 外部属性文件
          • 基于注解方式
    • 源码探究
    • 循环依赖

IOC

概念

控制反转、依赖注入,相当于咱不需要自己创建对象,不需要关注对象创建的过程了,要啥对象直接向Spring容器索要即可(Spring 通过反射创建),而我们只需要告诉它需要什么样的对象(bean,bean定义,xml或者注解配置)

控制反转,把对象创建和对象之间的调用过程交给Spring管理,为了降低耦合度。

优点

  • 控制反转和依赖注入降低了应用的代码量。

  • 松耦合,便于维护。

  • 支持加载服务时的饿汉式初始化和懒加载。

    单例模式可以分为懒汉式和饿汉式。
    懒汉式就是创建对象时比较懒,先不急着创建对象,在需要加载配置文件的时候再去创建。
    饿汉式就是在系统初始化的时候我们已经把对象创建好了,需要用的时候直接拿过来用就好了。

    惰性加载机制(或懒加载、延时加载),也就是说只有当使用到这个实例的时候才会创建这个实例

底层原理

  • XML解析、工厂模式、反射。

原始方式:耦合度非常高

Spring系列--IOC详解_第1张图片

工厂模式

Spring系列--IOC详解_第2张图片

XML配置文件

Spring系列--IOC详解_第3张图片

IOC接口

  • IOC思想基于IOC容器完成,IOC容器底层就是对象工厂。

  • Spring提供两种实现IOC容器的方式:

    • BeanFactory:IOC容器的基本实现,Spring内部的接口,一般不提供开发人员使用。

      • 特点:加载配置文件的时候并不会创建bean,在用到的时候才会创建。
    • ApplicationContext:BeanFactory的子接口,提供更多强大功能,一般开发人员使用。

      • 特点:加载配置文件的时候同时创建bean。
      • ApplicationContext接口实现类
        • FileSystemXmlApplicationContext(“盘符路径(绝对路径)”)
        • ClassPathXmlApplicationContext(“src目录下类路径”)

IOC操作

基于XML方式

  • Spring 创建对象

  • 创建普通类,类里面创建普通方法

    public class User{
        public void add(){
             system.out.println("add....");
         }
    }
    
  • 创建Spring配置文件,在配置文件中创建对象,新建base.xml配置文件

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        
        <bean id="user" class="com.spring5.User">bean>
    beans>
    
  • 在Bean标签有很多属性,常用的属性:id、class、name

  • 创建对象的时候,默认也是执行无参数构造方法,若没有无参构造则报错。

  • Spring注入属性

  • DI 依赖注入,注入属性

第一种注入方式:使用set方法进行注入

原始方法一:创建类,定义属性和对应的set方法

public class Book {
    private String bname;
    private String bauthor;
public void setBname(String bname) {
    this.bname = bname;
}
public void setBauthor(String bauthor) {
    this.bauthor = bauthor;
}
public static void main(String[] args) {
    Book book = new Book();
    book.setBname("WeiSanJin");
}

**Spring:**在Spring配置文件配置对象创建,配置属性注入,仍需要有set方法


<bean id="book" class="com.spring5.Book">

    <property name="bname" value="WeiSanJin">property>
    <property name="bauthor" value="WeiSanJin">property>
bean>
第二种注入方式:使用有参数构造进行注入

原始方法二:创建类,定义属性,创建属性对应有参数构造方法

public class Orders {
    private String oname;
    private String address;

    public Orders(String oname, String address) {
    this.oname = oname;
    this.address = address;
}
}

**Spring:**在spring 配置文件中进行配置,利用 ****标签

<bean id="orders" class="com.spring5.Orders">
    <constructor-arg name="oname" value="WeiSanJin">constructor-arg>
    <constructor-arg name="address" value="WeiSanJin">constructor-arg>
    
bean>

测试

@Test
public void TestOrder(){
    //1.加载Spring配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("base1.xml");

    //2.获取配置创建的对象
    Orders orders = context.getBean("orders",Orders.class);

    System.out.println(orders.toString());
}

P名称空间注入(了解)

  • 使用P名称空间注入是为了简化XML的配置方式,直接把属性写在bean标签内即可,不用再写property标签。

  • 第一步要添加P名称空间在配置文件中。

    
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
  • 属性注入,直接bean标签中操作。

    <bean id="book" class="com.spring5.Book" p:bname="WeiSanJin" p:bauthor="WeiSanJin">bean>bean>
    

其它类型属性注入(xml)

字面量

字面量为等号右侧的值,字面量是由字符串,数字等构成的字符串或数值,是固定的
  • null值:通过标签实现

  • 包含特殊符号的属性值,要么用转义字符,要么使用CDATA格式

    //方法一:转义字符
    <property name="address" value="<北京&dt;">property>
    //方法二:CDATA
    <property name="address">
        <value>
        ]]>
        value>
    property>
    

外部bean

通俗理解就是一个类中需要用到了另外一个类的对象,就是给一个bean注入一个外部bean,就需要用到ref属性。

<bean id="userService" class="com.spring5.service.UserService">  //对象一
        
        <property name="userDao" ref="userDaoImpl">property>//注入对象二
    bean>
	// 配置dao对象
<bean id="userDaoImpl" class="com.spring5.dao.UserDaoImpl">bean> //对象二 也就是外部bean

内部bean

<bean id="emp" class="com.spring5.bean.Emp">
        
        <property name="ename" value="WeiSanJin">property>
        <property name="genfer" value="WeiSanJin">property>
        <property name="dept">
            <bean id="dept" class="com.spring5.bean.Dept">
                <property name="dname" value="保安部">property>
            bean>
        property>
bean> //可以改用外部bean的方式来写 其它bean对象就不能调用。

级联赋值

    
    <bean id="emp" class="com.spring5.bean.Emp">
        
        <property name="ename" value="WeiSanJin">property>
        <property name="genfer" value="WeiSanJin">property>
        
        <property name="dept" ref="dept">property>
    bean>
    <bean id="dept" class="com.spring5.bean.Dept">
        <property name="dname" value="财务部">property>
    bean>

这三种赋值方式都有些什么区别?

集合类属性注入

 
    <bean id="stu" class="com.spring5.collectionytpe.Stu">
        
        <property name="courses">
            <array>
                <value>Java课程value>
                <value>数据库课程value>
            array>
        property>
        
        <property name="list">
            <list>
                <value>张三value>
                <value>小三value>
            list>
            //如果集合里的存储的是其它bean对象,就需要引用,同时也需要配置引用的bean
            <list>
                <ref bean="course1">ref>
                <ref bean="course2">ref>
            list>
        property>
        
        <property name="maps">
            <map>
                <entry key="Java" value="java">entry>
                <entry key="PHP" value="php">entry>
            map>
        property>
        
        <property name="sets">
            <set>
                <value>Mysqlvalue>
                <value>Redisvalue>
            set>
        property>
    bean>


    <bean id="course1" class="com.spring5.collectionytpe.Course">
        <property name="cname" value="String">property>
    bean>
    <bean id="course2" class="com.spring5.collectionytpe.Course">
        <property name="cname" value="String">property>
    bean>

util命名空间

应为beans只能存在bean对象,不能有list等集合标签。而util相当于将bean中的集合属性property抽出来单独当做一个bean对象操作,通过它就可以生成集合对象供其它bean引用。

  • spring配置文件中引入名称空间util

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
    
    • 使用util标签完成list集合提取

       
      <util:list id="bookList">
           <value>三国演义value>
           <value>水浒传value>
           <value>西游记value>
           <value>红楼梦value>
      util:list>
      
          
      <bean id="book" class="com.spring5.collectionytpe.Book">
           <property name="list" ref="bookList">property>
      bean>
      

IOC容器初始化

  • 初始化过程:BeanDefinition的资源定位、解析、注册。

    • 从XML读取配置文件。
    • 解析成BeanDefinition,并注入到BeanDefinition实例中。
    • 将BeanDefinition注册到容器BeanDefinitionMap中。
    • BeanFactory根据BeanDefinition的定义信息创建实例化和初始化Bean。
  • 单例Bean的初始化以及依赖注入一般都在容器的初始化阶段进行,除非设置了懒加载 lazy-inint为true的单例bean就是在第一次调用getBean() 方法的会后进行初始化和依赖注入。

  • 多例Bean在容器启动时不实例化,必须要在getBean调用的时候才实例化。

  • loadBeanDefinitions 采⽤了模板模式,具体加载 BeanDefinition 的逻辑由各个⼦类完成。

FactoryBean

Spring 有两种类型bean,一种普通bean,另外一种工厂bean(FactoryBean)

(1)普通bean在配置文件中,定义bean类型就是返回类型

(2)工厂bean在配置文件中定义bean类型可以和返回类型不一样

  • 第一步创建类,让这个类作为工厂Bean,实现接口FactoryBean

  • 第二步实现接口里的方法,在实现方法中定义返回的bean类型

    //实现接口类
    public class MyBean implements FactoryBean<Course>{
        @override
        public Course getObject() throws Exceptions{ //定义你要返回的类型
                Course  course = new Course();
                course.setCourse("java");
                return course;
        }
        @override
        public Class<?> getObjectType(){
                return NULL;
        }
        @override
        public Boolean isSingleton(){
                return false;
        }
    }
    //配置类
    <bean id = "mybean" class="com.zhh.entity.MyBean"></bean>
    //测试类
    @test
    public void test2(){
            ApplicationContext application  = new classPathXpthXmlApplicationContext("bean4.xml");
    Course course =  context.getBean("MyBean",Course.class);
    }
    

    工厂bean的意义在哪?

    首先它是一个Bean,但又不仅仅是一个Bean。它是一个能生产或修饰对象生成的工厂Bean。

  • 一个Bean如果实现了FactoryBean接口,那么根据该Bean的名称获取到的实际上是getObject()返回的对象,而不是这个Bean自身实例,如果要获取这个Bean自身实例,那么需要在名称前面加上’&’符号。

  • 通常是⽤来创建⽐较复杂的bean,⼀般的bean 直接⽤xml配置即可,但如果⼀个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,直接⽤xml配置⽐较⿇烦,这时可以考虑⽤FactoryBean,可以隐藏实例化复杂Bean的细节。

从源码分析作用:https://cloud.tencent.com/developer/article/1553476

Bean生命周期

简单来说Bean的生命周期主要包含四个方面:

  • 实例化对象
  • 初始化对象
  • 使用对象
  • 销毁对象

仔细细化来看,首先来看实例化对象部分

实例化

Spring系列--IOC详解_第4张图片

1、3、4中的方法均实现InstantiationAwareBeanPostProcessor接口

package com.Spring.Boot.init;
 
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor;
import org.springframework.stereotype.Component;
 
@Component
public class MyInstantiationAwareBeanPostProcessor implements InstantiationAwareBeanPostProcessor {
 
    // 实例化前置
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        
        System.out.println("postProcessBeforeInstantiation被调用了----在对象实例化之前调用-----beanName:" + beanName);
        // 默认什么都不做,返回null
        return null;
    }
 
    // 实例化后置
    @Override
    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInstantiation被调用了---------beanName:" + beanName);
        //默认返回true,什么也不做,继续下一步
        return true;
    }
    
    // 属性修改
    @Override
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
        System.out.println("postProcessPropertyValues被调用了---------beanName:"+beanName);
        // 此方法可对bean中的属性值进行、添加、修改、删除操作;
        // 对属性值进行修改,如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用,
        return pvs;
    }
}
  • 实例化前置

    使用的是 InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation(Class beanClass, String beanName) 方法,顾名思义,就是对在对象实例化之前对bean对象的class信息进行修改或者扩展,以达到我们想要的功能,它的底层是动态代理AOP技术实现的;且是bean生命周期中最先执行的方法;

​ 默认是返回一个null值的,也可以返回其它类型的值,当返回非空时,就不会进行下一步实例对象了,当用到这个bean的时候,返回的就是设置的这个返回值了。

  • 实例化对象

    doCreateBean方法创建实例,用反射技术创建,相当于new了一个对象,实例化了但是属性还没设置。

  • 实例化后置

    InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation(Object bean, String beanName),该方法的返回值决定着要不要进行下一步属性修改,返回false就不会执行下一步了。在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置

  • 属性修改

​ postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
​ 此方法可对属性值进行修改,修改范围包括添加、修改、删除操作;

初始化

Spring系列--IOC详解_第5张图片

  • 给用户属性赋值

用户属性指的是用spring 的人自定义的bean对象属性,像 User、Student、Teacher 、UserService、IndexService 这类的对象都是自定义bean对象,第5步主要给这类属性进行赋值操作,使用的是 AbstractAutowireCapableBeanFactory.populateBean() 方法进行赋值;

  • 给容器属性赋值

容器自带的属性,这些属性都是spring本来就有的;可以肯定的是,它们都是 Aware 接口的实现类。

Spring系列--IOC详解_第6张图片

package com.Spring.Boot.init.aware;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.*;
import org.springframework.context.annotation.ImportAware;
import org.springframework.context.weaving.LoadTimeWeaverAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.stereotype.Component;
import org.springframework.util.StringValueResolver;
import org.springframework.web.context.ServletContextAware;
import javax.servlet.ServletContext;
 
@Component
public class AllAwareInterface  implements BeanNameAware, BeanClassLoaderAware,
        BeanFactoryAware, EnvironmentAware, EmbeddedValueResolverAware,
        ResourceLoaderAware, ApplicationEventPublisherAware, MessageSourceAware,
        ApplicationContextAware, ServletContextAware, LoadTimeWeaverAware, ImportAware {
 
    @Override
    public void setBeanName(String name) {
        // BeanNameAware作用:让Bean对Name有知觉
        //这个方法只是简单的返回我们当前的beanName,听官方的意思是这个接口更多的使用在spring的框架代码中,实际开发环境应该不建议使用
        System.out.println("1 我是 BeanNameAware 的 setBeanName 方法  ---参数:name,内容:"+ name);
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        //获取Bean的类装载器
        System.out.println("2 我是 BeanClassLoaderAware 的 setBeanClassLoader 方法");
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        // 注意: 如果使用 @Configuration 注解的话,setBeanFactory方法会执行2次,
        System.out.println("3 我是 BeanFactoryAware 的 setBeanFactory 方法");
    }
    @Override
    public void setEnvironment(Environment environment) {
        //在工程启动时可以获得application.properties 、xml、yml 的配置文件配置的属性值。
        System.out.println("4 我是 EnvironmentAware 的 setEnvironment 方法");
    }
    @Override
    public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {
        System.out.println("5 我是 EmbeddedValueResolverAware 的 setEmbeddedValueResolver 方法");
    }
    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        //用来加载外部资源的;方法中有个参数:ResourceLoader ,这个参数其实就是ApplicationContext(spring 的上下文对象);
        System.out.println("6 我是 ResourceLoaderAware 的 setResourceLoader 方法");
    }
    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
        System.out.println("7 我是 ApplicationEventPublisherAware 的 setApplicationEventPublisher 方法");
    }
    @Override
    public void setMessageSource(MessageSource messageSource) {
        //国际化消息通知操作
        System.out.println("8 我是 MessageSourceAware 的 setMessageSource 方法");
    }
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("9 我是 ApplicationContextAware 的 setApplicationContext 方法");
    }
    @Override
    public void setServletContext(ServletContext servletContext) {
        System.out.println("10 我是 ServletContextAware 的 setServletContext 方法");
    }
    @Override
    public void setLoadTimeWeaver(LoadTimeWeaver loadTimeWeaver) {
        //LoadTimeWeaver 简称LTW,LTW是AOP的一种实现方式,此方法是为了获取Aop织入的对象,使用的织入方式是:类加载期织入,
        // 一般的aop都是运行期织入,就是在运行的时候才进行织入切面方法,但是LTW是在类加载前就被织入了,也就是class文件在jvm加载之前进行织入切面方法
        // 只有在使用 @EnableLoadTimeWeaving 或者存在 LoadTimeWeaver 实现的 Bean 时才会调用,顺序也很靠后
        System.out.println("11 我是 LoadTimeWeaverAware 的 setLoadTimeWeaver 方法");
    }
    @Override
    public void setImportMetadata(AnnotationMetadata annotationMetadata) {
        //只有被其他配置类 @Import(XX.class) 时才会调用,这个调用对 XX.class 中的所有 @Bean 来说顺序是第 1 的。
        System.out.println("12 我是 ImportAware 的 setImportMetadata 方法");
    }
}

EmbeddedValueResolverAware.setEmbeddedValueResolver()

通常我们使用@Value注解来获取properties 和 yml 文件中的值,每个类中都要使用@Value也很繁琐,实现EmbeddedValueResolverAware接口后就方便多了。用法也跟@Value一样,需要用${}包裹住;

@Component   
public class PropertiesUtil implements EmbeddedValueResolverAware {
 
	@Override
	public void setEmbeddedValueResolver(StringValueResolver stringValueResolver) {   
     System.out.println(stringValueResolver.resolveStringValue("${logging.file}"));
	}
}

ApplicationEventPublisherAware.setApplicationEventPublisher()

ApplicationEventPublisherAware是一个事件发布器的接口,使用这个接口,我们自己的 Service 就拥有了发布事件的能力。用户注册后,不再是显示调用其他的业务 Service,而是发布一个用户注册事件。那么在这里是发布事件,那就肯定有监听事件的接口,这个接口叫做 ApplicationListener ,只要实现 ApplicationListener 接口就可以接受发布的事件了。

  • 初始化前置

方法名称: BeanPostProcessor.postProcessBeforeInitialization()

在每一个 Bean 初始化之前执行的方法(有多少 Bean 调用多少次)

注意 : 启用该方法后,标注了@PostConstruct注解的方法会失效

  • 初始化后置

方法名称: BeanPostProcessor.postProcessAfterInitialization()

在每一个 Bean 初始化之后执行的方法(有多少 Bean 调用多少次)

初始化前置和初始化后置的实现代码如下

  • 初始化方法

初始化方法有三个,分别是 添加了@PostConstruct 注解的方法、实现InitializingBean接口、在@bean注解上添加 initMethod属性;

  • @PostConstruct

在bean对象内添加@PostConstruct 注解后即可实现初始化的功能,被@PostConstruct修饰的方法会在构造函数之后,init()方法之前运行。 有多个则会执行多次;

  • InitializingBean.afterPropertiesSet()

spring 初始化方法之一,作用是在BeanFactory完成属性设置之后,执行自定义的初始化行为。

执行顺序:在initMethod之前执行,在@PostConstruct之后执行

  • init-method

bean 配置文件属性 init-method 用于在bean初始化时指定执行方法,用来替代继承 InitializingBean接口,

注意的一点是只有一个类完整的实例被创建出来后,才能走初始化方法。

使用中

到这一步,bean对象就已经完全创建好了,是一个完整对象了,并且正在被其他对象使用了;

销毁

在这里需要先说一下,被spring容器管理的bean默认是单例的,默认在类上面有个 @Scope注解。

销毁和单例和多例有关,如果是单例模式,会先执行 DisposableBean.destroy()方法,然后在执行 destroy-Method 方法;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.stereotype.Component;
 
/**
 * 销毁方法
 */
@Component
public class ExtDisposableBean implements DisposableBean {
    @Override
    public void destroy() throws Exception {
        System.out.println("我被销毁了");
    }
}

//销毁方法和init一样 需要创建类再配置
package com.Spring.Boot.init;
 
import com.Spring.Boot.init.bean.BeanTest;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
 
@Component()
public class InitMethod  {
 
    // 在@Bean注解上添加initMethod属性,指向类中的 initMethod_1 执行初始化方法
    // 在@Bean注解上添加destroyMethod属性,指向类中的 destroyMethod_1 执行销毁方法
    @Bean(initMethod = "initMethod_1",destroyMethod = "destroyMethod_1")
    public BeanTest getBeanTest(){
        return new BeanTest();
    }
}
BeanTest.java

package com.Spring.Boot.init.bean;
 
public class BeanTest {
 
    // 将要执行的初始化方法
    public void initMethod_1(){
        System.out.println("我是beanTest的init方法");
    }
 
    // 将要执行的销毁方法
    public void destroyMethod_1(){
        System.out.println("我是beanTest的init方法");
    }
 
 
}

<bean id="beanTest" class="com.BeanTest" destroy-method="destroyMethod_1"></bean>

因为多例模式下,spring无法进行管理,所以将生命周期交给用户控制,用户用完bean对象后,java垃圾处理器会自动将无用的对象进行回收操作;

Bean的作用域

  1. singleton:单例,Spring中的bean默认都是单例的。
  2. prototype:每次请求都会创建⼀个新的bean实例。
  3. request:每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP request内有效。
  4. session:每⼀次HTTP请求都会产⽣⼀个新的bean,该bean仅在当前HTTP session内有效。
  5. global-session:全局session作⽤域。

xml自动装配

根据指定装配规则(属性名称或者属性类型),Spring自动将匹配的属性值进行注入


    <bean id="emp" class="com.spring5.autowire.Emp" autowire="byName">
    bean>
    <bean id="dept" class="com.spring5.autowire.Dept">bean>

外部属性文件

数据库信息配置

  • 直接配置

    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    
    <context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
    
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${prop.driverClass}">property>
            <property name="url" value="${prop.url}">property>
            <property name="username" value="${prop.userName}">property>
            <property name="password" value="${prop.password}">property>
        bean>
    
    基于注解方式
  • 格式:@注解名称(属性名=属性值,属性名=属性值)
  • 使用注解:注解作用在类(方法,属性)上
  • 使用目的:简化xml配置

Spring针对Bean管理中创建对象提供注解

  • @Component 普通用法
  • @Service 用于service业务逻辑层
  • @Controller 用于web层
  • @Repository 用于DAO持久层
<!-- 1、引入依赖-->
spring-aop-5.2.6.RELEASE
    
<!-- 2、引入context名称空间-->
<!-- 3、开启组件扫描,多个包,使用逗号隔开,或者写共同的上层目录-->
<context:component-scan base-package="com.zhh.service,com.zhh.DAO"></context:component-sacn>

	<!-- 开启组件扫描-->
<context:component-scan base-package="com.spring5"/>
   
    <!-- 创建类并使用注解||value值默认值为首字母小写的类名-->
    @Component(value = "User")
public class User {
    public void add(){
        System.out.println("service add......");
    }
}

	<!-- 测试方法-->
    @Test
    public void testService(){
        //1.加载Spring配置文件
        ApplicationContext context = new ClassPathXmlApplicationContext("base9.xml");

        //2.获取配置创建的对象
        User user = context.getBean("User", User.class);

        user.add();
    }

开启组件扫描细节配置

    
    <context:component-scan base-package="com.spring5" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

    
    <context:component-scan base-package="com.spring5">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    context:component-scan>

基于注解方式显示属性注入

**@AutoWired:**根据属性类型进行自动装配

@Service
public class StuService {
    // 定义dao类型属性(不需要添加set方法)
    // 添加注入属性组注解
    @Autowired
    private StuDao stuDao;

    public void add(){
        System.out.println("service add.....");
        stuDao.add();
    }
}

@Test
public void testService(){
    //1.加载Spring配置文件
    ApplicationContext context = new ClassPathXmlApplicationContext("base9.xml");

    //2.获取配置创建的对象
    StuService stuService = context.getBean("stuService", StuService.class);

    stuService.add();
}

**@Qualifier:**根据属性名称进行注入

@Repository(value = "StuDaoImpl1")
public class StuDaoImpl implements StuDao{

    @Override
    public void add() {
        System.out.println("dao add ......");
    }
}
@Service
public class StuService {
    // 定义dao类型属性(不需要添加set方法)
    // 添加注入属性组注解
    @Autowired
    @Qualifier(value = "StuDaoImpl1") // 根据名称注入
    private StuDao stuDao;

    public void add(){
        System.out.println("service add.....");
        stuDao.add();
    }
}

@Resource

@Service
public class StuService {
    //  @Resource  // 根据类型注入
    @Resource(name = "StuDaoImpl1")  // 根据名称注入
    private StuDao stuDao;

    public void add(){
        System.out.println("service add.....");
        stuDao.add();
    }
}

@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。

①如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。

②如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。

③如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。

④如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。

@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。

@Value

@Value注解时Spring框架中的一个常用功能,其作用是通过注解将常量、配置文件中的值、其他bean的属性值注入到变量中,作为变量的初始值。

@Service
public class StuService {
//    @Resource  // 根据类型注入
    @Resource(name = "StuDaoImpl1")
    private StuDao stuDao;

    @Value(value = "WeiSanJin")
    private String name;

    public void add(){
        System.out.println("service add....."+name);
        stuDao.add();
    }

完全注解开发

@Configuration
@ComponentScan(basePackages = {"com.spring5"})
public class SpringConfig {}

@Test
public void testService(){
    //1.加载Spring配置文件
    ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
    //2.获取配置创建的对象
    StuService stuService = context.getBean("stuService", StuService.class);

    stuService.add();
}

源码探究

BeanDefinitionReader

image-20221005150812447

不同的配置文件,只需要设置对应的解析方式,最后都是加载到BD(BeanDefinition)中去,因此就有了BeanDefinitionReader接口,不同的配置文件对应不同的实现子类。(体现了框架的扩展性

**补充基础:**抽象类和接口的区别?

语法上的区别:一个是interface一个是abstract

​ 1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;

4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

涉及层面的区别:接口是自上向下的,而抽象类是自下向上的。

  • 接口更像是一种约束,它能约束实现类你必须拥有什么样的功能,实现什么样的方法,但不能约束实现类不做什么功能。
  • 抽象类目的在于减 少代码复用,当多个类拥有共同的特性的时候,可以将这部分代码抽象出来,通过继承的方式让子类获得这部分代码的实现。

image-20221005160550602

Spring系列--IOC详解_第7张图片

  • AbstractBeanDefinitionReader:实现了 EnvironmentCapable,提供了获取/设置环境的方法。定义了一些通用方法,使用策略模式,将一些具体方法放到子类实现。
  • XmlBeanDefinitionReader:读取 XML 文件定义的 BeanDefinition
  • PropertiesBeanDefinitionReader:可以从属性文件,Resource,Property 对象等读取 BeanDefinition
  • GroovyBeanDefinitionReader:可以读取 Groovy 语言定义的 Bean

AbstractBeanDefinitionReader

Spring系列--IOC详解_第8张图片

该类是实现了 BeanDefinitionReader 和 EnvironmentCapable 接口的抽象类,提供常见属性:工作的 bean 工厂、资源加载器、用于加载 bean 类的类加载器、环境等。

核心方法:loadBeanDefinitions

Spring系列--IOC详解_第9张图片

当传入的参数为资源位置数组时,进入上述方法,如果为字符串数组,则挨个遍历调用。根据资源加载器的不同,来处理资源路径,从而返回多个或一个资源,然后再将资源作为参数传递给 loadBeanDefinitions(resources)方法。在该类中存在一个 loadBeanDefinitions(Resource... resources)方法,该方法用于处理多个资源,归根结底,最后还是调用 loadBeanDefinitions((Resource)resource)方法。

XmlBeanDefinitionReader

image-20221005162115289

这里也体现了,是通过将配置文件转换成流,再通过sax或者dom4j等解析方式,转换成document加载注册。

这个类的具体使用如下:

Spring系列--IOC详解_第10张图片

可以判断出IOC的使用过程,首先获取资源,创建bean工厂,通过工厂创建reader对象,再通过reader加载配置文件。

  • 获取资源
  • 获取 BeanFactory
  • 根据新建的 BeanFactory 创建一个BeanDefinitionReader对象,该Reader 对象为资源的解析器
  • 装载资源 整个过程就分为三个步骤:资源定位、装载、注册,如下:

Spring系列--IOC详解_第11张图片

其中注册就是通过BeanDefinitionRegistry 接口来实现的。本质上是将解析得到的 BeanDefinition 注入到一个 HashMap 容器中,IoC 容器就是通过这个 HashMap HashMap 来维护这些 BeanDefinition 的。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(new EncodedResource(resource));
}public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if (this.logger.isTraceEnabled()) {
        this.logger.trace("Loading XML bean definitions from " + encodedResource);
    }//获取已经被加载的资源集合中的资源集合,如果为null,则开辟空间
    Set<EncodedResource> currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if (currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }//判断currentResources中是否包含encodedResource,如果有则抛出异常,没有则加入
    if (!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
        try {
            //获取Resource对应的字节流
            InputStream inputStream = encodedResource.getResource().getInputStream();try {
                //使用字节流创建新的输入源
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    //设置编码
                    inputSource.setEncoding(encodedResource.getEncoding());
                }//该方法就是创建BeanDefinition的关键
                var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                inputStream.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if (((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }}return var5;
    }
}

loadBeanDefinitions(resource) 是加载资源的真正实现,从指定的 XML 文件加载 Bean Definition,这里会对 Resource 封装成 EncodedResource,主要是为了对 Resource 进行编码,保证内容读取的正确性。封装成 EncodedResource 后,调用 loadBeanDefinitions(encodedResource),方法 doLoadBeanDefinitions() 为从 xml 文件中加载 Bean Definition 的真正逻辑。

postProcessor

image-20221005165431841

循环依赖

Spring系列--IOC详解_第12张图片

核心方法:

getBean() -> dogetBean() -> createBean() -> docreateBean() -> createBeanInstance() -> docreateBeanInstance()

需要用两个map将半成品对象和成品对象分开,那为啥还要用到三级缓存呢?

  • 三个map也就是三级缓存中,分别存储的什么对象?

    • 一级缓存:成品对象
    • 二级缓存:半成品对象
    • 三级缓存:lambda表达式(getEarlyBeanReference())
  • 三个map缓存的查找顺序是什么样的?

    • 先查一级缓存找不到二级,再找不到三级。
  • 如果只有一个map缓存,能不能解决循环依赖的问题?

    • 不能,如果只有一个map,成品和半成品都要放在一个map中,而半成品对象是不能给外部使用的,所以必须做区分,否则就有可能暴露半成品。
  • 如果只有两个map缓存,能否解决循环依赖问题?

    • 能,但是有前提条件:循环依赖中不包含aop的处理逻辑
  • 为什么三级缓存能够解决循环依赖中包含代理对象的问题?

    • 创建代理对象的时候是否需要创建原始对象?

      • 需要
    • 同一个容器中能否出现同名的两个不同的对象。

      • 不能
    • 如果一个对象被代理,那么代理对象跟原始对象应该如何去存储。

      • 如果需要代理对象,那么代理对象创建完成之后应该覆盖原始对象
    • 在某个步骤中,出现了覆盖?

      • 在getEarlyBeanReference中,会判断是否需要代理对象,如果有了,就要覆盖。
    • 在对象对外暴露的时候,如何准确的给出原始对象或者代理对象,因为正常创建代理对象的地方在BPP的后置处理器中,而解决循环依赖问题的时候没执行到那里,因此就用了lambda表达式了,就是一种回调机制,在程序判断需要对外暴露的时候,就执行表达式。

Spring系列--IOC详解_第13张图片

依赖循环debug:

一路进入到Spring核心方法AbstractApplicationContext类中的refresh方法:

image-20221006162058233

在走完this.prepareBeanFactory(beanFactory)方法时,Spring已经生成了一个beanFactory,并且通过watch可以看到,一级缓存中此时存放了一些容器属性。

image-20221006162402485

再一直走,此时跳过一些不重要的逻辑处理,找到第一个getBean方法,此时的目的是创建A对象。

image-20221006162842413

进入该方法,发现调用了doGetBean方法,继续进入

image-20221006162943817

image-20221006164449261

可以看到这里有个getSingleton方法,但此时容器中没有A对象的单例实现,还没有到创建对象的环节,所以为null,再往下走,又发现了一个getSingleton方法。

Spring系列--IOC详解_第14张图片

这里的跟上面的有一些不一样,这里传进来的除了bean名称之外还有一个lambda表达式,进入这个方法,此处singletonFactory就是传进来的表达式

image-20221006164805688

而这里调用getObject方法相当于执行表达式内容。

Spring系列--IOC详解_第15张图片

至此,发现了一个关键方法createBean,创建对象刚刚开始,进入,同样也是doCreateBean方法。

image-20221006165031668

继续进入,找到了一个createBeanInstance方法,这个方法就是来创建对象实例的,也就是用了反射

image-20221006165131797

image-20221006170439085

image-20221006170522115

至此A对象就已经创建好了一个实例,并存了个lambda表达式到三级缓存,并且准备为这个A对象填充属性

image-20221006170706185

拿到属性名b

image-20221006171556900

image-20221006172046348

这里拿到的是一个b的运行时引用类型对象,不是我们所需要的B类对象,此时就需要做处理了

Spring系列--IOC详解_第16张图片

这个处理的过程其实就是开始创建对象了,循环正式开始了,此时三级缓存中存的是A类对象的lambda。

image-20221006172237421

image-20221006172320581

image-20221006172338827

容器中和缓存中依旧是没有b对象,因此开始重复上面步骤。一直到需要给b对象赋值,如果没有做出处理,就又需要创建A对象,循环往复,因此这里用了三级缓存做处理。

循环依赖总结

  • A创建对象 -> 实例化得到原始对象-> 通过原始对象生成对象工厂存入三级缓存(提前暴露) -> 属性注入B对象
  • B创建对象 -> 实例化得到B的原始对象 ->通过该原始对象生成对象工厂存入三级缓存(提前暴露) -> 需要注入A对象
  • 从三级缓存中获取对象工厂,通过调用getObject方法进行生成A对象(或者是代理对象),将该对象存入二级缓存。
  • B对象注入了未初始化的A对象,完成生命周期 ,返回该对象,完成A的属性注入,再经过生命周期完成创建。

两个问题:

  • 三级缓存什么情况下才有用?需要有AOP的情况下才有用,没有AOP的循环依赖存入到三级缓存中的对象工厂其实什么事也没做,直接返回对象。

因为,Spring的设计原则在于,AOP代理对象的生成一般是在初始化阶段的后置处理器中实现,而如果这样做,在循环依赖发生的情况下,最后经过完整生命周期的对象是该对象的代理对象,但是,在属性注入的时候,仍然注入的是原始对象,就会出现矛盾。

  • B对象中注入的是未初始化的A对象,这样做合理吗?

合理,因为后面所有步骤操作的A对象都是注入那个A对象的引用,都指向同一个地址,后面A完成生命周期的同时,B中的A也会一起完成,成为一个完整的A对象。

你可能感兴趣的:(Java,spring,java,后端)