尚硅谷-Spring5.2.6-源码讲解课程-IoC容器篇

阅读建议:笔记内容有点长,建议知识点复习或查缺补漏时享用~

IoC容器

Inversion of Control 控制反转,目的是降低代码间的耦合度(耦合是不可避免的,只能降低)。

解释:把对象的创建和对象之间的调用过程,都交由Spring进行管理。

常见的实现方式有:依赖注入和依赖查找

IoC容器(底层原理)

总结:XML解析 + 工厂设计模式 + 反射

创建工厂类,其中实现了XML解析,获取了对应的类名和属性名,通过反射实现类的实例化。

class UserFactory {
    public static UserDao getDao() {
        String classValue = class属性值; //1.XML解析
        Class clazz = Class.forName(classValue); 
        return (UserDao)clazz.newInstance(); //2.通过反射创建对象实例
    }
}

BeanFactory接口

IoC是一种思想,这种思想体现在IoC容器的实现上,IoC容器的底层逻辑就是一个对象工厂。

Spring对于IoC容器的设计,提供了两种方式,对应两个接口

  • BeanFactory
  • ApplicationContext

二者的异同点

相同点:都能实现XML配置文件的解析

不同点:

①BeanFactory是最基本、最内层的实现方式,Spring内部使用的接口,不提供给开发人员使用;ApplicationContext是BeanFacory的子接口,提供更多更强大的功能,一般由开发人员使用。

②BeanFactory在加载配置文件时不会创建对象实例,而是在获取bean时才会创建;而ApplicationContext在加载配置文件时就会创建对象实例。

@Test
    public void test01() {
        //BeanFactory在加载配置文件时不会创建对象实例,而是在获取bean时才会创建
        Resource resource = new ClassPathResource("beanbase.xml");
        BeanFactory factory = new XmlBeanFactory(resource);
        System.out.println("获取Bean前...");
        User user = factory.getBean("user", User.class);
        System.out.println(user);
    }

@Test
    public void test02() {
        //ApplicationContext在加载配置文件时就会创建对象实例
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beanbase.xml");
        System.out.println("获取Bean前...");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }

日常开发建议

关于加载配置文件时创建对象与否的问题,考虑到用户交互体验,即用户操作的响应效率问题,推荐日常开发中使用ApplicationContext接口实现。

ApplicationContext接口主要实现类

  • FileSystemXmlApplicationContext
  • ClassPathXmlApplicationContext

见名知意,FileSystemXmlApplicationContext实现类的传参就是物理文件系统的路径,如果是windows操作系统,就是哪个盘符balabala对应的路径。

@Test
    public void test03() {
        //ApplicationContext在加载配置文件时就会创建对象实例
        ApplicationContext applicationContext = new FileSystemXmlApplicationContext("E:\\Java\\01 Spring5\\Spring_5_2_6\\XMLConfig\\src\\beanbase.xml");
        System.out.println("获取Bean前...");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user);
    }

IoC容器(Bean管理)

Bean管理包括两个操作:创建对象、注入属性。

基于XML配置文件方式

XML配置文件头部

<?xml version="1.0" encoding="UTF-8"?>
<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">


    
</beans>
  1. 创建对象

public class Book {
    private String bookName;

    //若声明了带参构造方法,则一定要记得声明无参构造方法
    public Book() {
    }

    //带参构造方法,用于带参构造方法注入属性
    public Book(String bookName) {
        this.bookName = bookName;
    }

    public void setBookName(String bookName) {
        this.bookName = bookName;
    }
}
<bean id="book" class="com.coffeeship.dao.Book"/>

bean标签,常用的属性:id(唯一标识)、class(类的全路径名)、name(作用类似id,不常用)

id在ApplicationContext接口子类对象通过getBean获取对象时会指定,建议类名的小驼峰即可。

默行认执的就是无参构造方法,这就是之后使用注解创建自定义bean的时候,要求一定有无参构造方法的原因,不过一般也通过注解实现无参和全参的构造方法。

对应以前的原始方法就是new对象:

Book book = new Book();

还有其他的博主说创建对象有三种方式,除了上述那种,还有以下两种:

通过静态方法创建对象        和        通过实例方法创建对象

public class CreateBean {
    //通过静态方法
    public static Book getBookStatic() {
        return new Book();
    }

    //通过实例方法
    public Book getBook() {
        return new Book();
    }
}

factory-bean属性,用于指定创建当前bean对象的工厂bean id,指定此属性之后,class属性失效。factory-method属性,用于指定创建当前bean对象的工厂方法,和class属性配合使用时,方法必须是static的;和 factory-bean配合使用时,class属性失效。

<!--通过静态方法方式创建对象-->
<!--<bean id="book" class="com.coffeeship.dao.CreateBean" factory-method="getBookStatic"/>-->
<!--通过实例方法方式创建对象-->
<bean id="createBean" class="com.coffeeship.dao.CreateBean"/>
<bean id="book" factory-bean="createBean" factory-method="getBook"/>
  1. 注入属性

DI:dependency input 依赖注入

方式一:set方法注入

property标签,属性name即类中的属性名,value即赋值

<bean id="book" class="com.coffeeship.dao.Book">
    <property name="bookName" value="Java从入门到精通"/>
</bean>

对应以前的原始方法就是执行set方法:

Book book = new Book();
book.setBookName("Java从入门到精通");

方式二:带参构造方法注入

constructor-arg标签,属性name即类中的属性名,value即赋值

<bean id="book" class="com.coffeeship.dao.Book">
    <constructor-arg name="bookName" value="Java从入门到精通"/>
</bean>

对应以前的原始方法就是通过带参构造方法创建对象: 

Book book = new Book("Java从入门到精通");

注入属性的优化:p名称空间简化XML配置

<?xml version="1.0" encoding="UTF-8"?>
<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 id="book" class="com.coffeeship.dao.Book" p:bookName="Java从入门到精通"/>

​
</beans>

 注入属性-特殊字面量值:空值或包含特殊符号

<bean id="book" class="com.coffeeship.dao.Book">
    <property name="bookName">
        <null></null>
    </property>
</bean>

 特殊符号的处理方式一:使用转义字符,&lt;等

<bean id="book" class="com.coffeeship.dao.Book">
    <property name="bookName" value="&lt;&lt;Java从入门到精通&gt;&gt;"/>
</bean>

 特殊符号的处理方式一:使用<value><![CDATA[值的内容]]></value>

<bean id="book" class="com.coffeeship.dao.Book">
    <property name="bookName">
        <value><![CDATA[Java从入门到精通]]></value>
    </property>
</bean>

 注入属性:外部bean

property标签中不使用value属性,而使用ref属性,ref属性的值为外部bean的id

<bean id="emp" class="com.coffeeship.dao.Emp">
    <property name="empName" value="coffeeship"/>
    <property name="dept" ref="dept"/>
</bean>
<bean id="dept" class="com.coffeeship.dao.Dept"/>

注入属性:内部bean

直接在property标签内声明bean

<bean id="emp" class="com.coffeeship.dao.Emp">
    <property name="empName" value="coffeeship"/>
    <property name="dept">
        <bean id="dept" class="com.coffeeship.dao.Dept"/>
    </property>
</bean>

插播一个可能会有小伙伴有困惑的点!内部bean和外部bean是什么意思??

外部bean:直接在beans标签内部直接定义的bean对象,外部bean可以被多个bean对象引用

内部bean:在某个bean标签的内部定义的bean对象,内部bean只能被某个对象的某个属性引用。

注入属性:级联赋值

 方式一:

<bean id="emp" class="com.coffeeship.dao.Emp">
    <property name="empName" value="coffeeship"/>
    <property name="dept">
        <bean id="dept" class="com.coffeeship.dao.Dept">
            <!--层层嵌套,即所谓级联赋值-->
            <property name="deptName" value="开发部"/>
        </bean>
    </property>
</bean>

方式二:要求对应内部bean由相应的get方法

<bean id="emp" class="com.coffeeship.dao.Emp">
    <property name="empName" value="coffeeship"/>
    <property name="dept.deptName" value="开发部"/>
</bean>

 注入属性:数组、集合数据类型

public class Student {
    private String[] courses;
    private List<String> clubs;
    private Map<String, Integer> grades;
    private Set<String> hobbies;

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public void setClubs(List<String> clubs) {
        this.clubs = clubs;
    }

    public void setGrades(Map<String, Integer> grades) {
        this.grades = grades;
    }

    public void setHobbies(Set<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Student{" +
                "courses=" + Arrays.toString(courses) +
                ", clubs=" + clubs +
                ", grades=" + grades +
                ", hobbies=" + hobbies +
                '}';
    }
}

xml配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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="student" class="com.coffeeship.dao.Student">
        <!--数组类型注入,使用array标签或list标签均可-->
        <property name="courses">
            <array>
                <value>数据结构与算法</value>
                <value>离散数学</value>
                <value>计算机操作系统</value>
            </array>
        </property>
        <!--list类型注入,使用list标签-->
        <property name="clubs">
            <list>
                <value>篮球俱乐部</value>
                <value>瑜伽俱乐部</value>
            </list>
        </property>
        <!--map类型注入,使用map标签-->
        <property name="grades">
            <map>
                <entry key="数学" value="88"/>
                <entry key="英语" value="99"/>
            </map>
        </property>
        <!--set类型注入,使用set标签,并添加重复值,验证set的无重复性-->
        <property name="hobbies">
            <set>
                <value>手工</value>
                <value>手工</value>
                <value>学习</value>
            </set>
        </property>
    </bean>

</beans>

 测试代码

@Test
public void test05() {
    //ApplicationContext在加载配置文件时就会创建对象实例
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("collectionbean.xml");
    Student student = applicationContext.getBean("student", Student.class);
    System.out.println(student);
}

结果

 注意,除了数组、列表、map、set外,还有properties。

<bean id="student" class="com.coffeeship.dao.Student">
    <property name="infos">
        <props>
            <prop key="class">三年一班</prop>
            <prop key="province">Hunan Yueyang</prop>
        </props>
    </property>
</bean>

注入属性:设定自定义类的集合属性

<bean id="student" class="com.coffeeship.dao.Student">
    <property name="books">
        <list>
            <ref bean="book1"/>
            <ref bean="book2"/>
        </list>
    </property>
</bean>

<bean id="book1" class="com.coffeeship.dao.Book">
    <property name="bookName" value="机器学习入门"/>
</bean>

<bean id="book2" class="com.coffeeship.dao.Book">
    <property name="bookName" value="Springboot源码分析"/>
</bean>

注入属性:自定义类的集合属性提取

要求:引入util名称空间(名称空间引入有规律,参考beans)

<?xml version="1.0" encoding="UTF-8"?>
<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 id="books">
        <value>机器学习入门</value>
        <value>Springboot源码分析</value>
        <value>MySQL从入门到精通</value>
    </util:list>
    <bean id="student" class="com.coffeeship.dao.Student">
        <property name="books" ref="books"/>
    </bean>

</beans>

 普通bean与FactoryBean(工厂Bean)

普通bean:上面自定义的bean标签中都注明了class,且定义的类型就是获取bean的实例时对应的类型,即普通bean;而工厂bean意味着,配置文件中定义的bean类型可以和返回的类型不一样。

工厂bean的实现方式:实现FactoryBean接口,重写getObject()方法

public class MyBean implements FactoryBean {

    //重写返回bean的方法,指定返回类型
    @Override
    public Student getObject() throws Exception {
        Student student = new Student();
        return student;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return FactoryBean.super.isSingleton();
    }
}
<bean id="mybean" class="com.coffeeship.dao.MyBean"/>
@Test
public void test07() {
    //ApplicationContext在加载配置文件时就会创建对象实例
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factorybean.xml");
    Student student = applicationContext.getBean("mybean", Student.class);
    System.out.println(student);
}

bean作用域

bean标签中scope属性的可选值

singleton:单例模式(默认)、prototype:多实例、 

singleton时,加载配置文件时就会创建对象,prototype时,获取bean时才会创建多实例对象

<bean id="user" class="com.coffeeship.dao.User" scope="singleton"></bean>

 

<bean id="user" class="com.coffeeship.dao.User" scope="prototype"></bean>

request:请求域、session:会话域

这两个属性值很少使用,涉及到web端时,request表示一个对象对应一个request域,session表示一个对象实例对应一个session会话域。

bean的生命周期

bean对象从创建到销毁的过程

  1. 通过构造器创建bean实例
  2. set方法注入属性或其他bean的引用
  3. 调用bean的初始化方法init-method
  4. 获取到bean,可以使用
  5. 容器关闭时,调用bean的销毁方法destroy-method
public class Life {
    private Integer age;

    public Life() {
        System.out.println("第一步!执行无参的构造方法");
    }

    public void setAge(Integer age) {
        System.out.println("第二步!set方法注入属性");
        this.age = age;
    }

    public void initMethod() {
        System.out.println("第三步!bean的初始化方法");
    }
    public void destroyMethod() {
        System.out.println("第五步!bean的销毁方法");
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<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="life" class="com.coffeeship.dao.Life" init-method="initMethod" destroy-method="destroyMethod">
        <property name="age" value="99"/>
    </bean>

</beans>
@Test
public void test09() {
    //ApplicationContext在加载配置文件时就会创建对象实例
//        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
    Life life = applicationContext.getBean("life", Life.class);
    System.out.println("第四步!获取到bean");
    System.out.println(life);
    //关闭容器,注意close()方法是ClassPathXmlApplicationContext实现类中的
    //若是ApplicationContext applicationContext = new ClassPathXmlApplicationContext("lifebean.xml");
    //需要进行强转 eg.((ClassPathXmlApplicationContext) applicationContext).close();
//        ((ClassPathXmlApplicationContext) applicationContext).close();
    applicationContext.close();
}

bean的后置处理器

在初始化方法执行前后分别执行before方法和after方法

方法:创建后置处理器类,实现BeanPostProcessor接口,重写其中的两个方法

public class MyBeanPost implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第三步!初始化方法前执行");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("第五步!初始化方法后执行");
        return bean;
    }
}

注意!后置处理器对xml配置文件中所有的bean均生效!

<?xml version="1.0" encoding="UTF-8"?>
<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="life" class="com.coffeeship.dao.Life" init-method="initMethod" destroy-method="destroyMethod">
        <property name="age" value="99"/>
    </bean>
    <!--配置后置处理器的bean-->
    <bean id="mybeanpost" class="com.coffeeship.beanpost.MyBeanPost"/>

</beans>

bean手动装配&自动装配

上述通过property标签中的value、ref等属性赋值的方式称为手动装配,而通过autowire属性指定装配规则(属性名或属性类型)进行装配,由Spring自动匹配属性和属性值注入。

autowire属性常用:byName | byType

注意,若属性值选用byType,则同一个类不能定义多个bean。

不装配:

    <!--不装配-->
<bean id="emp" class="com.coffeeship.dao.Emp">
    <!--自动装配-->
<!--    <bean id="emp" class="com.coffeeship.dao.Emp" autowire="byName">-->
</bean>
<bean id="dept" class="com.coffeeship.dao.Dept">
    <property name="deptName" value="开发部"/>
</bean>

不装配,dept为null 

自动装配,自动装入bean对象

引入外部属性文件.properties

要求在xml配置文件中引入context名称空间

classpath为类路径,即src目录。

<?xml version="1.0" encoding="UTF-8"?>
<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/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--直接赋值-->
<!--    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/db2024"/>
        <property name="username" value="root"/>
        <property name="password" value="qts0922"/>
    </bean>-->
    
    <context:property-placeholder location="classpath:jdbc.properties"/>

    <!--引入外部配置文件-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClassName}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.username}"/>
        <property name="password" value="${prop.password}"/>
    </bean>
</beans>
@Test
    public void test02() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("datasource.xml");
        DruidDataSource dataSource = applicationContext.getBean("dataSource", DruidDataSource.class);
        System.out.println(dataSource);
    }

基于注解方式

注解:代码的特殊标记

注解格式:@注解名(属性名=属性值,属性名=属性值...)

注解使用范围:类上、方法上、属性上、

注解的作用:简化xml配置,使代码更优雅

Spring中针对bean管理中创建对象,提供了如下4个注解

@Component:组件(通用)、@Service:业务层、@Controller:控制层(web层)、@Repository:持久层(dao层),但官方说明此4个注解在功能上无差异,建议service放service接口……起到见名知意的作用!

创建对象

要求除了核心依赖:bean、context、core、expression外

还需要引入依赖:aop、

开启组件扫描配置

要求xml配置文件引入context名称空间,通过component-scan标签实现

<?xml version="1.0" encoding="UTF-8"?>
<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/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    <!--开启组件扫描
        1.若扫描多个包,包名用逗号隔开
        2.或直接写包的上层目录
        -->
    <context:component-scan base-package="com.coffeeship"/>

</beans>
//Component注解中的value属性可忽略,默认是类型把首字母小写
//value属性相当于bean标签中的id属性
//@Component(value = "userService")
@Service
public class UserService {
    public void show() {
        System.out.println("userService is showing...");
    }
}

测试

@Test
public void test01() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
    UserService userService = applicationContext.getBean("userService", UserService.class);
    userService.show();
    System.out.println(userService);
}

输出

 关于组件扫描配置中的其他属性use-default-filters

<!--use-default-filters 是否使用默认的扫描包过滤器,默认是扫描base-package下所有的类
false表明不使用默认的过滤器,紧接着就需要自定义过滤器,若是false,且没设置任何include-filter,则不会扫描任何类-->
<!--    <context:component-scan base-package="com.coffeeship" use-default-filters="false">
    &lt;!&ndash;include-filter表明扫描包含哪些符合规则的类,比如注解类型是org.springframework.stereotype.Service的&ndash;&gt;
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>-->

<!--未赋值use-default-filters属性为false,说明使用默认的过滤器,则扫描 扫描包下的所有类-->
<context:component-scan base-package="com.coffeeship">
    <!--使用默认的过滤器了,则没必要设置include-filter,但可以设置exclude-filter,表明排除哪些符合规则的类-->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

注入属性

@Autowired 根据属性类型自动装配,Spring提供

@Qualifier 根据属性名称自动装配,Spring提供

@Resource 根据属性类型或属性名称注入,JDK提供(扩展包javax中)

以上注解的使用比较简单,需要注意的是,@Autowired是根据属性类型注入,假设注入的属性是一个接口的实现类(多态的方式),eg.UserDao userDao = new UserDaoImpl(),但接口的实现类可能有多个,则此时需要增加@Qualifier注解,它通常和@Autowired一起搭配使用,eg.@Qualifier("userDaoImpl")

@Service
public class UserService {

    //@Autowired,根据属性类型自动装配
    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;
    public void show() {
        System.out.println("userService is showing...");
        userDao.show();
    }
}
public interface UserDao {
    public void show();
}
@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void show() {
        System.out.println("userDaoImpl is showing...");
    }
}

@Resource 根据属性类型装配,@Resource(name="属性名") 根据属性名称装配 

@Service
public class UserService {

    //@Autowired,根据属性类型自动装配
//    @Autowired
    //@Qualifier("userDaoImpl")
//    @Qualifier(value = "userDaoImpl")
//    @Resource //根据属性类型自动装配
    @Resource(name="userDaoImpl")
    private UserDao userDao;
    public void show() {
        System.out.println("userService is showing...");
        userDao.show();
    }
}

字面量属性注入:@Value

@Service
public class UserService {

    //@Autowired,根据属性类型自动装配
//    @Autowired
    //@Qualifier("userDaoImpl")
//    @Qualifier(value = "userDaoImpl")
//    @Resource //根据属性类型自动装配
    @Resource(name = "userDaoImpl")
    private UserDao userDao;

//    @Value("脱口秀")
    @Value(value = "脱口秀")
    private String showName;
    public void show() {
        System.out.println("userService is showing...");
        userDao.show();
        System.out.println(showName);
    }
}

完全注解开发

将目前为止仍在xml配置文件中配置的组件扫描配置,转化为注解实现!解决:配置类

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

测试类

public class TestDemo {
    @Test
    public void test01() {
        //加载配置文件
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.show();
        System.out.println(userService);
    }

    @Test
    public void test02() {
        //加载配置类
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = applicationContext.getBean("userService", UserService.class);
        userService.show();
        System.out.println(userService);
    }
}

 注意:实际开发中直接使用@SpringbootApplication注解表明一个程序入口方法,本身又包含@Configuration等注解。

另外,引入外部配置文件,eg.jdbc.properties,用到的注解@PropertySource({"classpath:jdbc.properties"}),搭配@Value("${prop.driverClassName}")将配置文件中的键值进行属性注入。

@Import:引入其他配置类

@Bean:将方法返回对象加入IoC容器

IoC高级特性

Lazy-Init 延迟加载

<!--lazy-init属性,延迟加载bean意味着加载配置文件时并不会创建bean实例,而是getBean时才创建-->
<!--默认是false,即加载配置文件的时候就实例化对象了,注意,此属性仅对scope为singleton时有效-->
<bean id="user" class="com.coffeeship.dao.User"/>
<!--<bean id="user" class="com.coffeeship.dao.User" lazy-init="true"/>-->

默认加载配置文件时就已经创建Bean对象了:

设置lazy-init属性为true后:

延迟加载的全局配置 default-lazy-init="true"

<?xml version="1.0" encoding="UTF-8"?>
<beans default-lazy-init="true" 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">

延迟加载的应用场景

1.开启延迟加载一定程度上是可以提高容器的启动效率的

2.更常见的应用延迟加载,是对于一些不太常用的Bean,将它们设置为延迟加载,仅在偶尔使用时再去加载,没必要在容器启动时就一直占用资源。虽然,一直不用,可能会被垃圾回收掉。 

FactoryBean

注意区分BeanFactory,面试可能会问二者的区别,但其实二者根本没有可比性,仅仅都是接口而已。BeanFactory是容器的顶级接口,定义了容器的基础功能,比如Bean管理中的getBean()等等,可以与之进行区分的是其子接口ApplicationContext;而FactoryBean,即工厂Bean,需要和普通Bean进行区分对比,工厂Bean可以返回一个与传入的Class属性不一致的类对象,原因就在于其实现了FactoryBean接口中getObject()方法,在其中可以自定义返回的类对象。普通Bean返回的Bean对象就是传入的Class的对象。 

FactoryBean接口除了getObject()和getObejctType()方法外,还有一个isSingleton()方法,若实现了isSingleton(),且返回true,则创建的Bean实例会放到applicationContext(容器)的单例缓存池中,缓存池的数据结构就是一个Map,键是bean id,值就是Bean对象。

 在上面IoC容器的Bean管理 - 普通Bean和工厂Bean的案例中:

若将getBean中的bean id前加上'&',即可获取普通Bean。

@Test
public void test07() {
    //ApplicationContext在加载配置文件时就会创建对象实例
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("factorybean.xml");
//        Student student = applicationContext.getBean("mybean", Student.class);
//        System.out.println(student);
    MyBean myBean = applicationContext.getBean("&mybean", MyBean.class);
    System.out.println(myBean);

后置处理器

Spring提供的两种后置处理器的接口:

BeanPostProcessor:Bean级别(对象级别)

BeanFactoryPostProcessor:BeanFactory级别(容器级别)

参考Spring Bean的生命周期

第1步:实例化Bean

第2步:属性注入

第3步:调用BeanNameAware的setBeanName方法

第4步:调用BeanFactoryAware的setBeanFactory方法

第5步:调用ApplicationContextAware的setApplicationContext方法

第6步:调用BeanPostProcessor的预初始化方法

第7步:调用@PostConstruct,初始化后执行的方法

第8步:调用InitializingBean的afterPropertiesSet方法

第9步:调用自定义的初始化方法init-Method

第10步:调用BeanPostProcessor的后初始化方法

第11步:调用DisposableBean的destroy方法

第12步:调用自定义的销毁方法destroy-Method或@PreDestroy注解标识的方法

注意:@PostConstruct和@PreDestroy注解需要结合使用 Spring 框架或其他支持 JSR-250 注解的容器,以确保该注解生效。

 对Bean的生命周期更深层次的了解,还可以去学习和查阅一下BeanDefinition这个接口。

BeanFactoryPostProcessor接口基本很少使用。


总结&面试题

        关于IoC容器,首先,我们自己应该对它都有一个大概的理解。然后,应该懂得IoC容器的作用,再根据作用去构建底层逻辑。这个是个人的一个小看法,如有建议,欢迎交流!

1.你是怎么理解IoC的?

IoC,即控制反转。以前,对象的创建、对象之间复杂的调用关系都是由开发人员去负责的;而通过实现IoC,可以把这部分任务交给框架去负责。在Spring里,IoC思想是通过IoC容器实现的。

2.IoC容器的初始化过程

IoC容器要做的就是对象的创建和属性的注入。这两部分都可以通过XML配置和注解的方式实现。当我们通过ClassPathXmlApplicationContext去加载一个配置文件时,会调用其中的refresh()方法

public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, @Nullable ApplicationContext parent) throws BeansException {
        super(parent);
        this.setConfigLocations(configLocations);
        if (refresh) {
            this.refresh();
        }

    }

这个方法又是在其继承的非直接父类AbstractApplicationContext中实现的

    public void refresh() throws BeansException, IllegalStateException {
        synchronized(this.startupShutdownMonitor) {
            //刷新上下文环境
            this.prepareRefresh();
            //obtainFreshBeanFactory()是容器初始化的入口
            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();
            }

        }
    }

其中,比较重要的就是obtainFreshBeanFactory()方法。

    protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
        this.refreshBeanFactory();
        return this.getBeanFactory();
    }

后面一系列的方法调用,可以去最大的学习网站B站上搜索对应的源码解析视频,在写这篇笔记的时候,本人看了几遍这个视频,还是觉得脑子混乱,就不在这献丑了。加油! 

或者通过AnnotationConfigApplicationContext加载一个配置类。

3.依赖注入(属性注入)的方式?

注入属性的方式在上面介绍xml配置和注解中,分别是xml配置可以通过property标签、p名称空间等实现,以及注解通过@Autowired、@Qualifier、@Resource、@Value来实现。

参考优秀文章:

Spring中加载xml配置文件的六种方式_spring 添加xml配置文件-优快云博客

spring DI外部bean与内部bean的区别_spring内部bean和外部bean区别-优快云博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值