Spring IOC 学习笔记

基本概念

IOC定义

  • 全称Inversion of Control(控制反转),把创建对象的过程交给Spring进行管理。
  • 作用:
    • 在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
    • 反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向,改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

DI定义

  • Dependency Injection(依赖注入)。IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。

底层原理

  • xml解析(注解解析)、工厂模式、反射
  • xml解析:xml文件存储需要被创建的对象及其属性、配置的一些信息。通过xml解析,Java读入xml装载的信息。
    <bean id="user" class="com.du.bean.User"/>
    
  • 工厂模式:通过工厂类创建新的对象,不使用new的方法。
    class UserFactory{
    	public User getUser(){
    		String className;
    		Class clazz = Class.forName(className);
    		return (User)clazz.newInstance();
    	}
    }
    
  • 反射:实现动态地创建对象,可以根据动态变化的类名(xml文件配置等),创建相应类的对象,而无需将类写死在代码中。实现进一步解耦。
    譬如,JDBC需要操作数据库,但数据库的类型不确定(MySQL、SQL Server等等)。那么,通过反射就可以解决这个问题。将数据库的驱动类型写在配置文件中,代码通过配置文件读入,获取当前使用的数据库驱动名字,然后根据类名创建相应的对象。(各个数据库驱动类拥有相同的接口)。那么,往后如果要修改数据库的类型,只要修改配置文件即可,无需修改代码。
    Driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mysql?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&rewriteBatchedStatements=true
    user=root
    password=root
    
    public static Connection getConnection() throws Exception {
    //      导入properties文件
            Properties config = new Properties();
            config.load(ClassLoader.getSystemClassLoader().getResourceAsStream("config.properties"));
            Class<?> driverClass = Class.forName(config.getProperty("Driver"));
    //        底层已经做了,所以不需要自己注册了
    //        Driver driver = (Driver) driverClass.newInstance();
    //        DriverManager.registerDriver(driver);
            String url = config.getProperty("url");
            return DriverManager.getConnection(url, config);
        }
    

IOC容器的实现

  • IOC思想就是bean的容器,IOC容器底层就是对象工厂。有两种方式(接口):BeanFactoryApplicaitonContext
BeanFactory
  • IOC容器的实现,是Spring内部的实现接口,开发人员不使用这个接口。特点是,加载配置文件的时候,不创建对象,只有在获取对象的时候,才创建。
ApplicaitonContext
  • BeanFactory的子接口,提供更加强大的功能,一般由开发人员进行使用。加载配置文件时就会把配置文件对象进行创建。
    在这里插入图片描述
  • ApplicationContext 的主要实现类
    • ClassPathXmlApplicationContext :对应类路径下的XML格式的配置文件
    • FileSystemXmlApplicationContext :对应文件系统中的XML格式的配置文件
    • ConfigurableApplicationContext :是ApplicationContext 的子接口,包含一些扩展方法,refresh()close()ApplicationContext 具有启动、关闭和刷新上下文的能力。
    • WebApplicationContext:专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作

bean管理

  • 指两个操作:创建对象、注入属性。
  • bean管理操作一般有两种方式实现:基于xml配置的方式实现、基于注解的方式实现。

基于xml创建对象

构造对象

  • id:唯一标识
  • class:类全路径(包类路径)
  • 默认使用无参构造方法完成对象创建
    <bean id="user" class="com.du.spring5.bean.User"/>
    

依赖注入(DI)

  • 就是注入属性
  • 使用 set() 方法进行注入:需要保证指定属性存在set()方法
    <bean id="user" class="com.du.spring5.bean.User">
           <!-- set方法的属性配置 -->
           <property name="id" value="1"/>
           <property name="name" value="J"/>
       </bean>
    
  • 使用有参构造进行注入:需要保证存在指定的有参构造
    按属性名赋值
    <bean id="user1" class="com.du.spring5.bean.User">
        <!-- 构造器方法 -->
        <constructor-arg name="id" value="2"/>
        <constructor-arg name="name" value="K"/>
    </bean>
    
    按顺序赋值
    <bean id="book" class="com.atguigu.spring.bean.Book">
    	<constructor-arg value= "10010" index ="0"/>
    	<constructor-arg value= "Book01" index ="1"/>
    	<constructor-arg value= "Author01" index ="2"/>
    	<constructor-arg value= "20.2" index ="3"/>
    </bean >
    
    按类型区分不同的重载构造器
    <bean id="book" class="com.atguigu.spring.bean.Book" >
    	<constructor-arg value= "10010" index ="0" type="java.lang.Integer" />
    	<constructor-arg value= "Book01" index ="1" type="java.lang.String" />
    	<constructor-arg value= "Author01" index ="2" type="java.lang.String" />
    	<constructor-arg value= "20.2" index ="3" type="java.lang.Double" />
    </bean >
    
  • p名称空间注入
    – 第一步,添加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="user2" class="com.du.spring5.bean.User" p:id="3" p:name="L"/>
    

字面量

  • null
    <bean name="user4" class="com.du.spring5.bean.User">
        <property name="name">
            <!--空值-->
            <null/>
        </property>
    </bean>
    
  • 属性值包含特殊符号
    方法一:把<>进行转义 &lt;&gt;
    方法二:把带特殊符号内容写到CDATA
    <bean name="user6" class="com.du.spring5.bean.User">
        <property name="id" value="6"/>
        <property name="name">
            <!-- 特殊字符的处理方法二 -->
            <!-- <![CDATA[内容]]> -->
            <value><![CDATA[<><>]]></value>   <!-- 输出<><> -->
        </property>
    </bean>
    

外部注入bean

<!-- 外部注入bean -->
<bean class="com.du.spring5.Dao.Impl.UserDaoImpl" name="userDao"/>
<!-- 注入外部对象 通过ref=id值 -->
<bean class="com.du.spring5.Service.UserService" name="userService">
    <property name="userDao" ref="userDao"/>
</bean>

内部注入bean

<bean class="com.du.spring5.Service.UserService" name="userService2">
    <!-- 内部bean -->
    <property name="userDao">
        <bean class="com.du.spring5.Dao.Impl.UserDaoImpl"/>
    </property>
</bean>

注入集合属性

  • 方法一:在bean内部属性注入
    <bean name="student" class="com.du.spring5.bean.Student">
        <!--  数组注入  -->
        <property name="array">
            <array>
                <value>array1</value>
                <value>array2</value>
                <value>array3</value>
                <value>array4</value>
            </array>
        </property>
        <!--列表注入-->
        <property name="list">
            <list>
                <value>list1</value>
                <value>list2</value>
                <value>list3</value>
                <value>list4</value>
            </list>
        </property>
        <!--map注入-->
        <property name="map">
            <map>
                <entry value="value1" key="key1"/>
                <entry value="value2" key="key2"/>
                <entry value="value3" key="key3"/>
                <entry value="value4" key="key1"/>
            </map>
        </property>
        <!--set注入-->
        <property name="set">
            <set>
                <value>set1</value>
                <value>set2</value>
                <value>set3</value>
                <value>set1</value>
            </set>
        </property>
    </bean>
    
  • 方法二:定义外部列表
    首先, 引入名称空间
    <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="outer_list">
        <value>out1</value>
        <value>out2</value>
        <value>out3</value>
        <value>out4</value>
    </util:list>
    <!--将外部定义的列表,注入到属性中-->
    <bean name="student2" class="com.du.spring5.bean.Student">
        <property name="list" ref="outer_list"/>
    </bean>
    

    如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用。

  • 在集合里面设置对象类型值
    <!--在集合里面设置对象类型值-->
    <bean class="com.du.spring5.bean.Person" name="john">
        <property name="name" value="john"/>
    </bean>
    <bean class="com.du.spring5.bean.Person" name="jack">
        <property name="name" value="jack"/>
    </bean>
    <bean class="com.du.spring5.bean.User1" name="user1">
        <property name="persons">
            <list>
                <ref bean="john"/>
                <ref bean="jack"/>
            </list>
        </property>
    </bean>
    

通过工厂创建bean

静态工厂
  • 即调用工厂的静态方法创建bean,无需实例化工厂
    public class UserStaticFactory {
      private static int count = 0;
    
      public static User getUser(String name) {
        User user = new User();
        user.setId(count++);
        user.setName(name);
        return user;
      }
    }
    
    <!--  通过静态工厂生成新对象  -->
    <bean id="user1" class="com.du.spring5.bean.UserStaticFactory" factory-method="getUser">
        <constructor-arg value="user1"/>
    </bean>
    

    在bean中说明factory-method是哪个,就会直接调用该方法生产

实例工厂
  • 需要被实例化之后,再调用获取对象的方法。
    public class UserInstanceFactory {
    private static int count = 0;
    
      public User getUser(String name) {
        User user = new User();
        user.setId(count++);
        user.setName(name);
        return user;
      }
    }
    
    <!--通过实例工厂生成对象-->
    <bean id="userInstanceFactory" class="com.du.spring5.bean.UserInstanceFactory"/>
    <bean id="user2" class="com.du.spring5.bean.User" factory-bean="userInstanceFactory" factory-method="getUser">
        <constructor-arg value="user2"/>
    </bean>
    

    首先实例化工厂类,然后在实例化bean的时候,说明它的 factory-bean 是谁,调用的方法是哪个

FactoryBean
  • 是Spring的一个接口,为了避免上面需要繁琐地声明而创造的一个接口。实现该接口之后,Spring可以识别该类为工厂类,直接调用该 getObject() 方法实现生产。
  • 创建类,实现接口FactoryBean
  • 实现接口里面的方法,在实现的方法中返回的bean类型
  • 该方法类似于懒汉式,只有在调用的时候,才会生成实例,无论是单实例还是多实例
    public class UserFactoryBean implements FactoryBean<User> {
        private static int count = 0;
    
        @Override
        public User getObject() throws Exception {
            User user = new User();
            user.setName("name " + count);  // 可以通过外部文件的方式指定
            user.setId(count++);
            return user;
        }
    
        @Override
        public Class<User> getObjectType() {
            return User.class;
        }
    
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    调用 getObject 方法,生产对象。getObjectType 方法获取生产对象的Class对象。isSingleton()设定是否为单例模式,返回false说明不是单例模式,每次调用都会生成一个bean。

    <!-- 通过FactoryBean生成新对象 -->
    <bean id="user3" class="com.du.spring5.bean.UserFactoryBean"/>
    
    @Test
    public void test5() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
        User user1 = context.getBean("user1", User.class);
        User user1_2 = context.getBean("user1", User.class);
        System.out.println(user1);
        System.out.println(user1_2);
        System.out.println(user1 == user1_2);  // true
    
        User user2 = context.getBean("user2", User.class);
        User user2_2 = context.getBean("user2", User.class);
        System.out.println(user2);
        System.out.println(user2_2);
        System.out.println(user2_2 == user2);// true
    
        User user3 = context.getBean("user3", User.class);
        User user3_2 = context.getBean("user3", User.class);
        System.out.println(user3);
        System.out.println(user3_2);
        System.out.println(user3_2 == user3);// false
    }
    

作用域

  • 在Spring中,可以在<bean>元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的(默认为单实例的)
    <!--    设置bean的作用域-->
    <!--    非单例模式-->
    <bean id="user8" class="com.du.spring5.bean.User" scope="prototype"/>
    <!--    单例模式-->
    <bean id="user9" class="com.du.spring5.bean.User" scope="singleton"/>
    
  • singletonprototype区别
    第一,singleton单实例,prototype多实例
    第二,设置scope值是singleton时候,加载spring配置文件时候就会创建单实例对象;
    设置scope值是prototype时候,不是在加载 spring 配置文件时候创建对象,在调用getBean()方法时候创建多实例对象
    在这里插入图片描述

bean生命周期

步骤
  • 通过构造器创建bean实例(无参构造)
  • bean的属性设置值和其他bean引用(调用set方法)
  • 调用bean的初始化方法(需要进行配置初始化的方法)
  • bean可以使用了(对象获取到了)
  • 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
加上后置处理器
  • 通过构造器创建bean实例(无参构造)
  • bean的属性设置值和其他bean引用(调用set方法)
  • bean实例传递bean后置处理器的方法postProcessBeforeInitialization()
  • 调用bean的初始化方法(需要进行配置初始化的方法)
  • bean实例传递bean后置处理器的方法postProcessAfterInitialization()
  • bean可以使用了(对象获取到了)
  • 当容器关闭时候,调用bean的销毁的方法(需要进行配置销毁的方法)
演示bean生命周期
// bean
public class Person {
    private String name;
    public Person() {
        System.out.println("Constructor");
    }
    public void initMethod(){
        System.out.println("initMethod");
    }
    public void destroyMethod(){
        System.out.println("destroyMethod");
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("set");
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
// 后置处理器
public class PersonPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization");
        return bean;
    }
}
<!--bean的生命周期-->
   <!-- initMethod 初始化方法 -->
   <!-- destroyMethod 销毁方法-->
   <!-- 顺序: 构造器构造 -> set方法 -> 初始化方法initMethod -> bean可以使用 -> 销毁bean  -->
   <bean id="person" class="com.du.spring5.bean.Person" init-method="initMethod" destroy-method="destroyMethod">
       <property name="name" value="john"/>
   </bean>

   <!-- 配置处理器 -->
   <!-- 加上配置器之后,会在initMethod之间,加上postProcessBeforeInitialization和postProcessAfterInitialization方法 -->
   <!-- 顺序: 构造器构造 -> set方法 -> postProcessBeforeInitialization —> 初始化方法initMethod -> postProcessAfterInitialization -> bean可以使用 -> 销毁bean  -->
   <bean id="personPostProcessor" class="com.du.spring5.bean.PersonPostProcessor"/>
ApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
System.out.println("get bean");
Person person = context.getBean("person", Person.class);
// 接口没有close,只能强转回ClassPathXmlApplicationContext
ClassPathXmlApplicationContext context1 = (ClassPathXmlApplicationContext) context;
context1.close();

配置信息的继承

  • 如果两个 bean之间有很多重复的属性,又不想重复定义,那就可以通过parent继承bean
    <bean id="user" class="com.du.spring5.bean.User">
        <property name="name" value="john"/>
        <property name="id" value="1"/>
    </bean>
    <bean id="user1" class="com.du.spring5.bean.User" parent="user">
        <property name="id" value="2"/>
    </bean>
    

    user1 继承了 user 的属性,而自己修改了id的值

    @Test
      public void test9() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean9.xml");
        User user = context.getBean("user", User.class);
        User user1 = context.getBean("user1", User.class);
        System.out.println(user); // User{name='john', id=1}
        System.out.println(user1);  // User{name='john', id=2}
      }
    
  • 如果有一个bean只是用来当模板,而不想被实例化,可以通过abstract属性设置
        <bean id="user" class="com.du.spring5.bean.User" abstract="true">
        <property name="name" value="john"/>
        <property name="id" value="1"/>
    </bean>
    

    abstract="true" 表示这个不能被实例化

bean之间的依赖

  • 有的时候创建一个 bean 的时候需要保证另外一个 bean 也被创建,这时我们称前面的 bean 对后面的 bean 有依赖。
    <bean id="emp03" class="com.atguigu.parent.bean.Employee" depends-on="dept">
    	<property name="empId" value="1003"/>
    	<property name="empName" value="Kate"/>
    	<property name="age" value="21"/>
    </bean>
    

    一般, bean 的实例化都是按照顺序来的,但是如果在标签中加入了 depends-on ,那么会在该bean 被创建之前,先创建依赖的bean,无论顺序。

自动装配

  • 手动装配:以 valueref 的方式明确指定属性值都是手动装配
  • 自动装配:根据指定装配规则(属性名称或属性类型),Spring自动将匹配的属性值进行注入,autowire属性常用两个值:
    • byName:根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
    • byType:将类型匹配的bean作为属性注入到另一个bean中。
    public class School {
    	private String name;
    	/*省略*/
    }
    
    public class Student {
    	private School school;
    	/*省略*/
    }
    
    <!-- 自动装配技术 -->
    <!-- 无需自己配置property,让代码自己通过bean的name或类型,寻找对应的属性 -->
    <bean class="com.du.spring5.bean.School" id="school"/>
    <!-- 通过类型配置 -->
    <bean class="com.du.spring5.bean.Student" id="student" autowire="byType"/>
    <!--通过名字配置-->
    <bean class="com.du.spring5.bean.Student" id="student2" autowire="byName"/>
    

引用外部属性文件

  • 可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可
  • 首先,创建外部属性文件,properties格式文件,写数据库信息
    prop.driverClass=com.mysql.jdbc.Driver
    prop.url=jdbc:mysql://localhost:3306/mysql
    prop.userName=root
    prop.password=root
    
  • 引入context名称空间
    <!--根据外部配置文件,配置属性,需要导入名称空间-->
    <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
    ">
    
  • 引入外部属性文件
    <!-- 引入外部配置文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!-- 配置连接池 -->
    <!-- 后面就可以直接使用这个数据库连接池了 -->
    <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
        <property name="driverClassName" value="${prop.driverClass}"/>
        <property name="url" value="${prop.url}"/>
        <property name="username" value="${prop.userName}"/>
        <property name="password" value="${prop.password}"/>
    </bean>
    

SpEL

  • Spring Expression Language,Spring表达式语言,简称SpEL。支持运行时查询并可以操作对象图
  • 和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样
  • SpEL使用 #{…} 作为定界符,所有在大框号中的字符都将被认为是SpEL表达式
使用字面量
  • 这个用处不大,看看就好
  • 整数
    <property name="count" value="#{5}"/>
    
  • 小数
    <property name="frequency" value="#{89.7}"/>
    
  • 科学计数法
    <property name="capacity" value="#{1e4}"/>
    
  • String类型的字面量可以使用单引号或者双引号作为字符串的定界符号
    <property name=“name” value="#{'Chuck'}"/>
    <property name='name' value='#{"Chuck"}'/>
    
  • Boolean
    <property name="enabled" value="#{false}"/>
    
引用其他bean
<bean id="emp04" class="com.atguigu.parent.bean.Employee">
	<property name="empId" value="1003"/>
	<property name="empName" value="Kate"/>
	<property name="age" value="21"/>
	<property name="detp" value="#{dept}"/>
</bean>

应该和 ref 效果一样

引用其他bean的属性值作为自己某个属性的值
<bean id="emp05" class="com.atguigu.parent.bean.Employee">
	<property name="empId" value="1003"/>
	<property name="empName" value="Kate"/>
	<property name="age" value="21"/>
	<property name="deptName" value="#{dept.deptName}"/>
</bean>
调用非静态方法
<!-- 创建一个对象,在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.atguigu.spel.bean.SalaryGenerator"/>

<bean id="employee" class="com.atguigu.spel.bean.Employee">
	<!-- 通过对象方法的返回值为属性赋值 -->
	<property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>
调用静态方法
<bean id="employee" class="com.atguigu.spel.bean.Employee">
	<!-- 在SpEL表达式中调用类的静态方法 -->
	<property name="circle" value="#{T(java.lang.Math).PI*20}"/>
</bean>

运算符

<property name="key" value="#{1*2}"/>

这个应该还是有点用的

基于注解创建对象

  • 相对于XML方式而言,通过注解的方式配置bean更加简洁和优雅,而且和MVC组件化开发的理念十分契合,是开发中常用的使用方式

使用注解标识组件

  • 普通组件:@Component,标识一个受Spring IOC容器管理的组件
  • 持久化层组件:@Respository,标识一个受Spring IOC容器管理的持久化层组件
  • 业务逻辑层组件:@Service,标识一个受Spring IOC容器管理的业务逻辑层组件
  • 表述层控制器组件:@Controller,标识一个受Spring IOC容器管理的表述层控制器组件
  • 组件命名规则
    • 默认情况:使用组件的简单类名首字母小写后得到的字符串作为beanid
    • 使用组件注解的value属性指定beanid
  • 在类上面添加创建对象注解
    @Component(value="dptm")
    public class Department {
        private String name="dptm";
    
        public Department() {
        }
    
        public Department(String name) {
            this.name = name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Department{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    

    在注解中的value属性可以忽略不写,默认为类名称,首字母小写。

扫描组件

导包
  • 必须在原有JAR包组合的基础上再导入一个:spring-aop-4.0.0.RELEASE.jar
开启组件扫描
<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对象 -->
<context:component-scan base-package="com.du.spring5"/>
<context:component-scan base-package="com.du.spring5.bean, com.du.spring5.service"/>
  • base-package 属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类。
  • 当需要扫描多个包时可以使用逗号分隔。
  • 如果仅希望扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类
    <context:component-scan base-package="com.atguigu.component" resource-pattern="autowire/*.class"/>
    
  • 包含与排除
    • <context:include-filter>:子节点表示要包含的目标类。
      注意:通常需要将 use-default-filters 属性设置为 false ,禁用默认过滤器,然后扫描的就只是include-filter中的规则指定的组件了
    <!--    组件扫描细节配置-->
    <!-- use-default-filters="false" 表示不使用默认的filter 默认所有对象都会被扫描出来 如今设为false之后,就只包含include-filter里面的内容 -->
    <context:component-scan base-package="com.du.spring5" use-default-filters="false">
        <!--        筛选目录内Component的注解-->
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    
    • <context:exclude-filter>:子节点表示要排除在外的目标类。(无需禁用默认过滤器)
    <!--    组件扫描细节配置-->
    <!-- use-default-filters="true" 还是使用默认的filter,只是在这基础上,除去Component部分    -->
    <context:component-scan base-package="com.du.spring5">
        <!-- 筛选目录内Component的注解-->
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Component"/>
    </context:component-scan>
    
  • 过滤表达式
    类别示例说明
    annotationcom.atguigu.XxxAnnotation过滤所有标注了XxxAnnotation的类
    assignablecom.atguigu.BaseXxx过滤所有BaseXxx类的子类。
    aspectjcom.atguigu.*Service+所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。
    regexcom\.atguigu\.anno\.*所有com.atguigu.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤
    customcom.atguigu.XxxTypeFilter使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口

自动装配

  • Controller组件中往往需要用到Service组件的实例,Service组件中往往需要用到Repository组件的实例
  • 在指定要扫描的包时,<context:component-scan> 元素会自动注册一个bean的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理器可以自动装配标记了@Autowired@Resource@Inject注解的属性
@Autowired注解
  • 执行步骤如下:
    1. 首先,根据类型实现自动装配。若找到一个符合类型的对象,就直接装配。
    2. 若没找到任何一个符合类型的对象,那就报异常,装配失败。
    3. 若找到了多个符合类型的对象,则比较对象名和需要被装配的属性名(因为默认属性名就是id),找到那个名字相同的,实现装配
    4. 若符合类型的对象,名字都不符合,那就异常。
  • @Autowired根据属性类型自动装配
    @Service
    public class UserService {
        // 定义dao类型属性
        // 不需要添加set方法
        // 添加注入属性注解
        @Autowired
        private UserDao userDao;
        public void add() {
            System.out.println("service add....");
            userDao.add();
        }
    }
    
  • @Autowired中,可以指定一个参数required,默认为true,表示如果标注了,那就必须要被注入,否则就报异常。而如果给它设置为false,则可以实现找到了就注入,没找到就算了。
  • 构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用@Autowired注解。(也就是说,可以在方法上面标注一个@Autowired,这样就会在创建该对象的时候,顺便运行了该方法/构造器/属性,并注入相应的对象。)
  • 一般情况下,属性注入有三种方式:字段名上标注、构造器上标注、setter上标注。一般来说,建议使用setter上标注。参考
    @Component
    public class Phone {
      private String name;
      public Phone() {
      }
      public Phone(String name) {
        this.name = name;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      @Override
      public String toString() {
        return "Phone{" +
                "name='" + name + '\'' +
                '}';
      }
    }
    
    @Component
    public class User1 {
      @Autowired
      private Phone phone;
      @Autowired
      private User1(Phone phone) {
        this.phone = phone;
      }
      public User1() {
      }
      public Phone getPhone() {
        return phone;
      }
      @Autowired
      public void setPhone(Phone phone) {
        this.phone = phone;
      }
      @Override
      public String toString() {
        return "User1{" +
                "phone=" + phone +
                '}';
      }
    }
    
  • 如果存在一个对象集合/数组需要被@Autowired,那就会将所有符合的类型,全部注入到该集合/数组中。若该 Map 的键值为String,那么 Spring将自动装配与值类型兼容的bean作为值,并以bean的id值作为键。
    @Component
    public class Phone {
      private String name;
      public Phone() {
      }
      public Phone(String name) {
        this.name = name;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      @Override
      public String toString() {
        return "Phone{" +
                "name='" + name + '\'' +
                '}';
      }
    }
    
    @Component
    public class User2 {
      private Phone[] phones;
      private List<Phone> phoneList;
      private Map<String, Phone> phoneMap;
      public User2() {
      }
      public Phone[] getPhones() {
        return phones;
      }
      @Autowired
      public void setPhones(Phone[] phones) {
        this.phones = phones;
      }
      public List<Phone> getPhoneList() {
        return phoneList;
      }
      @Autowired
      public void setPhoneList(List<Phone> phoneList) {
        this.phoneList = phoneList;
      }
      public Map<String, Phone> getPhoneMap() {
        return phoneMap;
      }
      @Autowired
      public void setPhoneMap(Map<String, Phone> phoneMap) {
        this.phoneMap = phoneMap;
      }
      @Override
      public String toString() {
        return "User2{" +
                "phones=" + Arrays.toString(phones) +
                ", phoneList=" + phoneList +
                ", phoneMap=" + phoneMap +
                '}';
      }
    }
    
    <?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 https://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:component-scan base-package="com.du.spring5"/>
        <bean id="phone1" class="com.du.spring5.bean.Phone">
            <property name="name" value="phone1"/>
        </bean>
        <bean id="phone2" class="com.du.spring5.bean.Phone">
            <property name="name" value="phone2"/>
        </bean>
    </beans>
    
      @Test
      public void test5() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean4.xml");
        User2 user2 = context.getBean("user2", User2.class);
        System.out.println(user2);  // User2{phones=[Phone{name='null'}, Phone{name='phone1'}, Phone{name='phone2'}], phoneList=[Phone{name='null'}, Phone{name='phone1'}, Phone{name='phone2'}], phoneMap={phone=Phone{name='null'}, phone1=Phone{name='phone1'}, phone2=Phone{name='phone2'}}}
      }
    
@Qualifier注解
  • 当属性名和容器内的对象名不一致,无法实现注入的时候,可以在需要被自动注入的对象上面,再添加一个注解@Qualifier。用来指定该对象的id,用于指定需要被注入对象。
  • @Qualifier根据名称进行注入
    //@Component(value="user")
    @Component// 等效于上方
    public class User {
        @Value(value = "abc") // 用于注入普通类型属性
        private String name;
        private int id;
        // 自动属性注入,方法:需要被注入的类型属性,首先创建对象注解。然后在属性上面加Autowired
        @Autowired // 根据类型注入
        @Qualifier(value = "dptm") // 根据名称注入,应用于一个类型有多个不同名称的对象时候。如果类就可以定位,那么该注解可以省略
        private Department department;
    
        public User() {
        }
    
        public User(String name, int id) {
            this.name = name;
            this.id = id;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public void setDepartment(Department department) {
            this.department = department;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", id=" + id +
                    ", department=" + department +
                    '}';
        }
    }
    
@Resource
  • @Autowired 很类似,但没有 required 属性。此注解是JavaEE的注解,与@Autowired 不同的是,具有更好的拓展性。
@Inject
  • @Inject@Autowired 注解一样也是按类型注入匹配的bean,但没有reqired属性

泛型依赖注入

  • 可以为子类注入子类对应的泛型类型的成员变量的引用。
举例
  • java bean
    public class User {
    }
    
    public class Book {
    }
    
  • dao 层
    public interface BaseDao<T> {
      void save();
    }
    
    @Repository
    public class BookDao implements BaseDao<Book> {
      @Override
      public void save() {
        System.out.println("调用了BookDao");
      }
    }
    
    @Repository
    public class UserDao implements BaseDao<User> {
      @Override
      public void save() {
        System.out.println("调用了UserDao");
      }
    }
    
  • service 层
    public class BaseService<T> {
      BaseDao<T> baseDao;
    
      public void save() {
        baseDao.save();
      }
    
      public BaseDao<T> getBaseDao() {
        return baseDao;
      }
      @Autowired  // 在这里自动注入!
      public void setBaseDao(BaseDao<T> baseDao) {
        this.baseDao = baseDao;
      }
    }
    
    @Service
    public class BookService extends BaseService<Book> {
    }
    
    @Service
    public class UserService extends BaseService<User> {
    }
    
  • 调用
    public class ServiceTest {
      @Test
      public void testService() {
        ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
        BookService bookService = context.getBean("bookService", BookService.class);
        UserService userService = context.getBean("userService", UserService.class);
        bookService.save();  // 调用了BookDao
        userService.save();  // 调用了UserDao
      }
    }
    
  • 主要原理就是,两个service实现类都继承了 BaseService@Autowired也继承过去了。然而两个service 给dao设置了不同的泛型类型。因此,在自动匹配的过程中,除了匹配类型之外,还匹配了泛型,匹配到完全适合的时候,就自动注入了。
  • 因此实现了不同的泛型,自动注入了不同的对象。

完全注解开发

  • 创建配置类,替代xml配置文件
    • 第一步,创建配置类,替代xml配置文件
      @Configuration // 表示这是一个配置类,代替xml文件
      @ComponentScan(basePackages={"com.du.spring5"})  // 指定扫描的路径
      public class SpringConfig {
      }
      
    • 第二步,使用
      @Test
      public void test3() {
          // 通过另一个bean工厂的实现类,通过配置类的方式,导入配置。无需使用xml
          ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 和ClassPathXmlApplicationContext是远方表兄弟
          User user = context.getBean("user", User.class);
          System.out.println(user);
      }
      

整合多个配置文件

  • Spring允许通过<import>将多个配置文件引入到一个文件中,进行配置文件的集成。这样在启动Spring容器时,仅需要指定这个合并好的配置文件就可以。
  • import 元素的resource属性支持Spring的标准的路径资源
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值