Spring.IOC

1 vs. 工厂模式

public class ServiceFactory {
    public Object getServiceObject (String name) {
        if (name.equals("bs")) {
            A a = new A();
            B b = new B(a); // 构造器
            MyBatiscConnection conn = new MyBatiscConnection();
            LogUtil log = new LogUtil();
            BookService bookservice = new BookService();

            b.setA(a); // setter
            conn.setB(b);
            bookservice.setConn(conn);
            LogUtil.setConn(conn);
            bookservice.setLogger(log);

            return bookservice;
        }
    }
}

main () {
    ServiceFactory sf = new ServiceFactory();

    BookService bookservice = sf.getServiceObject("bs");
    bookservice.sellBook();
}

将工厂模式切割,分为解析程序和 xml 文件。

xml 暴露出来,用来 描述 工厂需要实例化的类以及他们之间的关系。

<xml>
  <!-- 之前命令;描述 -->
  <bean id="a" class="xxx.yyy.A" />
  <bean id="b" class="xxx.yyy.B">
    <property name="a" bean="a" />
  </bean>
  <bean id="conn" class="xxx.yyy.MyBConn">
    <property name="b" bean="b" />
  </bean>
  <bean id="log" class="xxx.yyy.LogUtil">
    <property name="conn" bean="conn" />
  </bean>
  <bean id="bookservice" class="xxx.BookService">
    <property name="conn" bean="conn" />
    <property name="log" bean="log" />
  </bean>
</xml>

2 接口

最核心的 jar 包:

  • spring-core
  • spring-bean
  • spring-context

最核心的接口是 BeanFactory,它用来描述 IOC 容器:

clip_2018-12-13_03-01-52.png

它很干净,很纯粹,最主要的方法是 getBean 用来给调用方返回一个实例化好的对象。

在实际运用中,需要一些周边功能,比如加载资源/国际化/等等,Spring 为此提供了 ApplicatinContext 接口。它本身是 BeanFactory 的一个实现:

clip_2018-12-13_03-04-54.png

可以看到,ApplicationContext 除了实现了 BeanFactory,还实现了其他一些 实用的接口。因此,它是在 Spring 中操作一切的核心。

这是 门面模式 的一种典型使用。

3 配置

3.1 传统的方式 xml

这种方式,充分利用了 xml 文件的优势:

  • 接受度比较高,语法简单
  • 表达能力比较强
  • 生态比较完整,基于 xml 的校验、解析等比较完善

所以,最开始的时候,描述工厂里 bean 声明的方式,选用的就是 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="bs1" class="learning.spring.BookServiceImpl" />
    <bean id="bs2" class="learning.spring.BookService2Impl" />

    <!--<bean id="bs2" class="learning.spring.BookServiceImplImpl" />-->

    <!--<bean id="bs3" class="learning.spring.BookServiceImplImpl" />-->
</beans>

但是:

  • 很多人不喜欢 xml 这种标签式的语法。写起来麻烦,看起来不舒服
  • xml 方式过于重型
  • xml 语法校验虽然强大,但不够强大
  • xml 虽然灵活,但不够灵活

所以,就产生了很多其他的叛逆的想法

3.2 基于 Java 的方式进行配置

@Configuration
public class SuibianSpringConfig {

    // <bean id="bs1" class="learning.spring.BookServiceImpl" />
    @Bean
    public BookService bs1() {
        if (Math.random() > 0.5) {
            // 构造器注入
            return new BookService2Impl(bookDAO());
        } else {
            // setter 注入
            BookService bs = new BookServiceImpl();
            bs.setBookDAO(bookDAO());
            return bs;
        }
    }

    @Bean
    public BookDAO bookDAO () {
        return new BookDAO();
    }
}

3.3 混合双打

Java Style 中混入 XML Style:

@Configuration
@ImportResource(locations = "learning/spring/my-spring.xml")
public class SuibianSpringConfig {

    // <bean id="bs1" class="learning.spring.BookServiceImpl" />
    @Bean
    public BookService bs1() {
        System.out.println(bookDAO());
        if (Math.random() > 0.5) {
            return new BookService2Impl();
        } else {
            return new BookServiceImpl();
        }
    }

    @Bean
    public BookDAO bookDAO () {
        return new BookDAO();
    }
}

XML style 中混入 Java Style:

<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="bs2" class="learning.spring.BookService2Impl" />
    <bean class="learning.spring.configuration.SuibianSpringConfig" />
</beans>

4 装配 (Wiring)

创建应用对象之间协作关系的行为,通常称为装配 (Wiring)。这是依赖注入 (DI) 的本质。

装配的基础,是使用配置文件对 Bean 的关系进行声明。

总结起来,在 Spring 中,声明 Bean 一共有三种方式:

  1. 在 XML Style 的配置中,使用 <bean /> 节点
  2. 在 Java Style 的配置中,使用 @Bean 注解
  3. 开启 Component 扫描,然后使用相关注解: @Component/@Controller/@Service/@Repository

4.1 Wiring in XML

装配:

<!-- 最简单的声明 -->
<bean class="package.Dog" /> <!-- Class.forName("package.Dog").newInstance(); -->
<bean id="xxx" class="包名.类的名字" />

<!-- 对象内部必须要有数据,所以要给他数据 -->

<!-- 构造器注入 -->
<bean id="donkey" class="learning.components.Donkey">
  <constructor-arg index="0" value="得水" />
  <constructor-arg index="1" value="333" />
</bean>

<!-- 属性注入 -->
<bean class="learning.components.Goal" id="goal">
  <property name="name" value="多利" />
  <property name="countOfLegs" value="4" />
</bean>

<!-- 集合的注入 -->
<bean class="learning.components.Goal" id="goal">
  <property name="name" value="多利" />
  <property name="legs">
     <list> <!-- map/arrary/set/prop 同理 -->
        <value>leg1</value>
        <value>leg2</value>
        <value>leg3</value>
     </list>
  </property>
</bean>

<!-- Bean 的依赖 -->
<bean class="learning.components.Goal" id="goal">
  <property name="xxx">
      <ref bean="xxx" />
  </property>
  <property name="yyy" ref="yyy" />
  <property name="zzz">
      <bean class="package.Zzz" />
  </property>
</bean>

<!-- 使用 C/P/UTIL 命名空间简化使用 -->
<bean class="learning.components.Goal" id="goal"
      p:name="多利" p:countOfLegs="4"
      p:xxx-ref="xxx" p:aliases-ref="aliaslist" />

<util:list id="aliaslist">
  <value>kkkk1</value>
  <value>kkkk2</value>
  <value>kkkk3</value>
</util:list>

<!-- 将实例化的工作,委托给工厂 -->
<bean class="learning.components.AnimalFactory" id="goalmiemie" factory-method="xxx" />
<bean class="learning.components.AnimalFactory" id="deshui" factory-method="yyy" />

<!-- 包含其他的配置文件到这里 -->
<import resource="xxxxx.xml" />

另外:

  • denpend-on 定义顺序
  • parent 定义继承
  • scope 定义初始化策略
  • lazy 延迟初始化
  • alias 定义别名
  • import 引入其他的定义文件

4.2 Wiring in JavaConfig

@Configuration
@ComponentScan("包名")
@ImportResource("..../xxx.xml")
@Import({ShiroConfig.class, MybatisConfig.class, ScheduleConfig.class})
public class SpringConfig {
    @Bean
    public Author author () {
        return new Author("luxun");
    }

    @Bean
    public Book book1 (Author author) {
        return new Book("呐喊", author);  // 参数注入
    }

    @Bean(name = "book3")
    public Book book2 () {
        return new Book("呐喊", author());  // 方法调用注入
    }
}

4.3 Wiring with Annotation

首先,需要开启 Component Scanning,即组件扫描:

// JavaConfig
@Configuration
@ComponentScan("xxx.components")
public class SpringConfig {
    // ...
}


// XMLConfig:
//
// <context:component-scan base-package="xxx.components" />

声明 Bean 有四个注解:

  1. @Component
  2. @Controller
  3. @Service
  4. @Repository

在注解的方式中,要确定 Bean 之间的关系,需要使用下面注解进行 “注入”:

  • @Autowired
  • @Inject
  • @Resource
  • @Value

注入的注解:

  • 可以放在 field 上,称作属性注入
  • 可以放在 constructor 上,称作构造器注入
  • 可以放在 setter 方法上,称作 setter 注入
@Controller
public class BookController {
    @Autowired // 属性注入
    BookDAO bookDAO;

    @Autowired // 构造器注入
    public BookController(BookDAO bookDAO) {
        this.bookDAO = bookDAO;
    }

    @Autowired // setter 注入
    public void setBookDAO(BookDAO bookDAO) {
        this.bookDAO = bookDAO;
    }
}

属性注入的写法是最简单的,但不建议使用。原因:

5 生命周期 (Lifecycle)

如果想在 Bean 实例化过程中,做一些额外的事情, 就需要了解 Bean 从产生,到使用,到最后销毁的整个过程。

也就是 Bean 的生命周期。

clip_2018-12-14_01-43-10.png

一个 Bean 从产生到消亡经历了这些过程:

  1. Spring 对 bean 进行实例化 (Instance)
  2. Spring 对 bean 的值或引用进行注入 (Populate Properties)
  3. 若实现了 BeanNameAware 接口,则调用 setBeanName 方法,即 BeanNameAware.setBeanName()
  4. BeanFactoryAware.setBeanFactory()
  5. ApplicationContextAware.setApplicationContext()
  6. BeanPostProcessor.postProcessBeforeInitialization()
  7. InitializingBean.afterPropertiesSet()。若声明了 init-method 方法,调用之
  8. BeanPostProcessor.postProcessAfterInitialzation()
  9. 此时 bean 准备就绪,就可以使用了,直到销毁
  10. DisposableBean.destroy()。若 bean 声明了 destory-method 属性,调用之

Author: unname

Created: 2019-03-22 周五 01:32

Go ahead, never stop.