Spring 知识 -- Spring IOC 的应用

本文最后更新于:4 个月前

  1. Spring 实现IoC的思路和方法

    1. Spring 实现 IoC 是通过配置信息来描述类与类之间的配置关系,然后由去解析这些配置信息

      步骤:

      1. 应用程序中提供类和依赖对象

      2. 通过配置,把所有需要用到的类交给容器 (schema,annotation,Java configuration)

      3. 再描述一遍类和依赖对象的关系

  2. Spring IoC 使用例子:

    1. 方式一: schema

      <beans>
          <!-- 声明类 -->
          <bean id="dao" class="ind.yinchao.ioc.test1.IndexDaoImpl"/>
      
          <!-- 声明类 -->
          <bean id="service" class="ind.yinchao.ioc.test1.IndexService">
              <!-- 声明依赖对象 -->
              <!-- 方法一: 基于 setter 方法,** name 和属性名无关,只和 setter 方法名有关** -->
              <property name="dao" ref="dao"/>
              <!-- 方法二: 基于构造函数 -->
              <!-- <constructor-arg ref="dao"/>-->
          </bean>
      </beans>

      但是请注意,这里有一个很坑的点,或者说我基础不扎实的地方:

      当使用方法一的时候,不要写任何构造器,因为如果不写还好,JVM 可以自动给你生成默认的空构造方法

      但是如果写了带依赖参数的构造器,那么 JVM 将不会为你生成默认的空构造方法,这样使用 setter 注入会报错 No default constructor found

      所以要么就不写带参数的构造方法(不和 构造函数注入 混用),要么,就再写上无参构造函数

      // 在主函数里面使用 ClassPathXmlApplicationContext 传入这个 xml 配置文件
      ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
      
      // getBean 的参数是 service,和类名其实无关,之和 xml 的 id 有关
      Service Service = (IndexService) classPathXmlApplicationContext.getBean("service");
      service.test();
    2. 方式二: annotation ( 与 schema 或者 Java Configuration 集成)

      1. 方法一:annotation + schema

        1. 首先需要开启 Spring 对注解的支持:

          在 xml 里面写上 <context:component-scan base-package="ind.yinchao.ioc.test1"/>

          这样离不开 xml,不太好

      2. 方法二:annotation + Java Configuration 更改主函数的 Context (已经体现 自动装配)

        新建 Java 配置文件

        // @configuration 就是生命这是代替 xml 的配置文件
        @Configuration()
        // @componentScan 开启扫描
        @ComponentScan("ind.yinchao.ioc.test2")
        public class SpringConfiguration{
        }
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);

      然后就可以使用注解了

      //声明类
      @Service
      public class Service{
          // 声明依赖关系
          @Autowired
          Dao dao;
      }
      
      <!-- 声明类 -->
      @Component("dao")
      public interface DaoImpl{
          public void test(){
              System.out.println("daoImpl1);
          }
      }
    3. 方式三: schema + annotation + Java Configuration 混合使用

      在 Java Configuration + annotation 的基础上,在 Java 配置文件类 SpringConfiguration 中加上 @ImportResource("SpringConfiguration.xml") 以启用 schema

    4. 使用 schema 的问题:

      如果我们类与类之间有了依赖关系,那么为什么还要在声明类的配置中来再描述一遍依赖对象呢?这样不就冗余了吗

      解决:

      使用 Spring 的 自动装配 完成上面的第三步,不需要在第二步完成类的描述之后再去进行冗余的类与依赖对象的描述

      在 xml 配置文件的头部最后加上 default-autowired = "no|byType|byName|construct",分别对应:

      1. no|default: 不自动装配

      2. byType: 自动属性注入

        需要 set 方法

        type: 依赖属性的类型

      3. byName 自动名字注入:

        需要 set 方法

        name: set方法按一定规律的名字

        例如 setTest ,那么 xml 中对应的依赖类 id 就为 test

      4. 自动通过构造方法注入:需要带参构造方法

        <!-- 在主程序的配置入口处配置 最后加上 default-autowired="byType"-->
        
        <bean id="dao" class="ind.yinchao.ioc.test1.IndexDaoImpl"/>
        
        <bean id="service" class="ind.yinchao.ioc.test1.IndexService">
        
        <property name="dao" ref="dao"/>
        </bean>
    5. 使用 annotation + Java Configuration 的问题 (schema 也类似,就不再举例)

      实现类有两个:

      @Component("dao2.1")
      public class IndexDaoImpl1 implements IndexDao{
          @Override
          public void test() {
              System.out.println("impl2.1");
          }
      }
      @Component("dao2.2")
      public class IndexDaoImpl2 implements IndexDao{
          @Override
          public void test() {
              System.out.println("impl2.2");
          }
      }

      这时如果在 Service 类里面继续写依赖就会报错

      public class Service{
          @Autowired
          IndexDao dao
      }

      No qualifying bean of type ‘test2.IndexDao’ available: expected single matching bean but found 2: dao2.1,dao2.2

      因为 @Autowired 默认是根据 byType 查找,这里出现了两个 IndexDao 的实现,那么就有两个相同的类型,就会出错

      解决:

      1. 方法一: @Autowired 是先根据 byType 如果没有找到,会使用 byName

        这样,那我们只需要修改 Service 里面的依赖变量名 dao -> indexDaoImpl1,这样就可以找到 IndexDaoImpl1 这个类了

        注意,和 setter 方法无关

      2. 使用 @Resource 是根据 byName 查找的,解决方式同上

      3. 虽然还是使用 @Resource ,这里我们还可以使用 @Qualifier 指定 Bean 的名字

    6. 作用域 Scope

      一个很坑的点: singleton bean 需要 prototype bean 的时候, 被依赖的 prototype bean 可能只会初始化一次,那么 prototype 就失效了

      解决

      1. implement ApplicationContextAware

        需要用到 Spring 的 API 侵入性很强

      2. 通过 @Lookup(“indexDaoImpl1”)注解

        @Lookup 写在一个 abstract 方法上,并返回依赖

        再将这个类设置为 abstract

        这个类就不需要 @Autowired 或者 @Resource 注解了,直接在外部调用 Service 的方法里面再调用那个 abstract 方法即可

        @Lookup(“indexDaoImpl1”),可以通过指定 bean 来防止有多个同类型的 type

        @Lookup() 实际上是依赖查找 DL!

    7. Bean 生命周期的回调 (lifecycle callback)

      创建 Bean 时的回调 (销毁时同理)

      1. 方式一: 实现 InitializingBean 接口,重写 afterPropertiesSet 方法

      2. 方法二: 对于基于 xml 的情况,可以在 bean 标签里加上

        <bean default-init-method="init">

        并指定方法名

      3. 方法三: 在方法上写一个注解 @PostConstruct