Hello Spring Framework——依赖注入(DI)与控制翻转(IoC)

又到年关了,还有几天就是春节。趁最后还有些时间,复习一下Spring的官方文档。

写在前面的话:

Spring是我首次开始尝试通过官方文档来学习的框架(以前学习Struts和Hibernate都大多是视频或书籍为主,文档一带而过)。除了语言上的障碍以外更困难的地方是对新概念的理解,这些都是过了很久才逐渐体会。要说有什么经验的话,那就是对于不同的文档都似乎有它们自己的上下文语境。虽然不可否认外国人在文档统一性方面已经做的非常出色了,但只要还有那么一点点差异在语言的“阻拦”下依旧会让我觉得深奥。仅这一点来说看中文的书籍或视频讲解确实学的更快。这次跟着创作博客的节奏又大致浏览一遍,希望能有新的发现。

***************************以下是正文的部分***************************

一、Ioc和DI

(1)概念

This chapter covers the Spring Framework implementation of the Inversion of Control (IoC) [1] principle. IoC is also known as dependency injection (DI). It is a process whereby objects define their dependencies, that is, the other objects they work with, only through constructor arguments, arguments to a factory method, or properties that are set on the object instance after it is constructed or returned from a factory method. The container then injects those dependencies when it creates the bean. This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.
Introduction to the Spring IoC container and beans

以上摘自Spring官方文档,大意是:Spring Framework实现了控制翻转功能,而控制翻转又需要依赖注入。在这个过程中,框架可以针对构造器、工厂模式以及Setter方法实现注入。由于整个过程完全自动,因此被称为控制翻转(IoC)。

(2)The Spring IoC container

Spring容器是元数据和配置文件的消费者,通过用户定义最终的产品由容器来生成。也就是说,作为Spring的使用者,你必须告诉容器如何使用Object,以及处理它们之间的相互依赖。

(3)初始化容器

ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
ApplicationContext

(4)使用容器

// 创建applicationContext
ApplicationContext context =
    new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});

// 实例化一个对象
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// 另一种更常用的实例化方式
PetStoreService service = (PetStoreService)context.getBean("petStore");

// 使用对象
List<String> userList = service.getUsernameList();
Using the container

二、通过XML的方式

(1)Bean overview

<?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="beanID" class="className" scope="singleton/prototype" lazy-init="true/false">
        <!-- 通过构造函数注入 -->
        <constructor-arg ref="args"/>
        <!-- 通过Setter方法注入 -->
        <property name="methodName" ref="anotherBeanID"/>
    </bean>
    <!-- 依赖关系的产生也需要通过Spring -->
    <bean id="anotherBeanID" class="..."/>
    <!-- more bean definitions go here -->

</beans>
常见XML配置

通常情况下,Spring生成的是单例对象并且会在容器初始化时就完成。可以配置scope="singleton/prototype"和lazy-init="true/false"来做调整。

(2)Collections

<bean id="beanName" class="className">
    <!-- 等同于java.util.Properties -->
    <property name="adminEmails">
        <props>
            <prop key="administrator">administrator@example.org</prop>
            <prop key="support">support@example.org</prop>
            <prop key="development">development@example.org</prop>
        </props>
    </property>
    <!-- 等同于java.util.List -->
    <property name="someList">
        <list>
            <value>a list element followed by a reference</value>
            <ref bean="myDataSource" />
        </list>
    </property>
    <!-- 等同于java.util.Map -->
    <property name="someMap">
        <map>
            <entry key="an entry" value="just some string"/>
            <entry key ="a ref" value-ref="myDataSource"/>
        </map>
    </property>
    <!-- 等同于java.util.Set -->
    <property name="someSet">
        <set>
            <value>just some string</value>
            <ref bean="myDataSource" />
        </set>
    </property>
</bean>
Java容器配置

三、通过Annotation的方式

(1)官方推荐使用annotation的方式,但是到底哪一种更适合你呢?

Are annotations better than XML for configuring Spring?

The introduction of annotation-based configurations raised the question of whether this approach is 'better' than XML. The short answer is it depends. The long answer is that each approach has its pros and cons, and usually it is up to the developer to decide which strategy suits them better. Due to the way they are defined, annotations provide a lot of context in their declaration, leading to shorter and more concise configuration. However, XML excels at wiring up components without touching their source code or recompiling them. Some developers prefer having the wiring close to the source while others argue that annotated classes are no longer POJOs and, furthermore, that the configuration becomes decentralized and harder to control.

No matter the choice, Spring can accommodate both styles and even mix them together. It’s worth pointing out that through its JavaConfig option, Spring allows annotations to be used in a non-invasive way, without touching the target components source code and that in terms of tooling, all configuration styles are supported by the Spring Tool Suite.
Are annotations better than XML for configuring Spring?

以上摘自Spring官方文档,大意是:annotation的方式相较XML的方式更加简练,而且分开配置可以更加自由。但是XML依然也是一种不错的选择,因为当你修改XML配置的时候,源代码不需要重新参与编译。总之就是萝卜青菜各有所爱咯!

(2)声明

<?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">

    <context:annotation-config/>

</beans>
annotation-config

实际上,通过上述方式声明的annotation源码依然需要在XML文件中配置<bean/>,更加常见的方式是直接让Spring去扫描指定的包路径:

<?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">

    <context:component-scan base-package="packageName"/>

</beans>
component-scan

注意:使用<context:component-scan>隐含了<context:annotation-config>。在有的视频上似乎是教大家两条需要同时配置,实际上在官方文档中写的很清楚。

(3)常用注解

根据Spring官方文档的描述,Spring4.x版本支持自己定义的annotation以及JSR-250和JSR-330的annotation。

i.@Required

在Setter方法上使用,初始化时如果没有匹配的对象注入Spring会抛出NullPointerExceptions。

ii.@Autowired

可以在方法和属相上设置,另外@Autowired(required=false)注解也可以规定注入不是必须的实现。

iii.@Configuration和@Bean

通过Java源码提供配置,@Configuration设置在类名上,@Bean设置在方法名上。 

package test.primary;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MovieConfiguration {
    @Bean
    public MovieCatalog firstMovieCatalog() {
        return new FirstMovieCatalog();
    }

    @Bean
    public MovieCatalog secondMovieCatalog() {
        return new SecondMovieCatalog();
    }
}
MovieConfiguration

iv.@Primary和@Qualifier

@Primary和@Bean配合使用,@Qualifier和@Autowired配合使用。使得实现了相同接口的对象能够准确注入。

v.@Resource

功能上等同于@Autowired,它是由JSR-250提供的注解。主要是依照对象名称注入。

vi.@Component

配置在类名上,提供给Spring扫描包使用。

vii.@ComponentScan

配置在类名上,提供给依赖注入的对象直接扫描包使用(不重要一般不会使用)。

viii.@Component,@Repository,@Service和@Controller

其它的可以配置在类名上,提供给Spring在不同的环境中通过包扫描机制区分各种类型。

(4)包扫描过滤器

根据Spring官方文档的定义,包扫描过滤器配置有5种类型。分别为annotation、assignable、aspectj、regex和custom。迄今为止,我只接触过annotation和regex两种方式,它们同时支持annotation与XML。由于@ComponentScan注解本身并不常用,因此一般是配合XML来用。

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    //...
}
appConfig
<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex"
                expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>
xmlConfig

(5)对JSR-330注解的支持

 JSR-330提供的注解相对比较简单,主要是@Inject和@Named。

import javax.inject.Inject;
import javax.inject.Named;

@Named
public class SimpleMovieLister {
    private MovieFinder movieFinder;
    
    @Inject
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...

}
SimpleMovieLister

此外@Named也可以被放置在方法的参数前面,语义类似@Qualifier。应该说JSR-330提供的注解更方便,只是由于需要导入新的包依赖关系,所以在很多开发场景中并不被推荐。

四、基于Java源码的容器配置

这一块好像绝大部分的中文教材上都没有特别讲解,我也是在第二次读文档的时候才注意到的(应该是很少被使用)。不过作为文档学习的一部分,我还是打算将这个部分大致的整理一下。

(1)@Comfiguration和@Bean

以下两种配置是相同的

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }

}
AppConfig
<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>
appConfig.xml

(2)使用AnnotationConfigApplicationContext

基于Java源码的容器配置,必须使用AnnotationConfigApplicationContext类来初始化容器。文档中提供了两种初始化的方式,分别对应XML中的<bean/>和<context:component-scan/>配置。实际上如果去读API文档就会发现,使用AnnotationConfigApplicationContext来初始化容器也可以在构造函数中直接完成。

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
注册方式
public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}
包扫描方式
public class SpringTest {
    @Test
    public void test() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        for (String s : context.getBeanDefinitionNames()) {
            System.out.println(s);
        }
    }
}
构造函数方式

提醒:配置了@Configuration和@Bean的类,只能通过构造函数或注册方式来解析内部的对象,包扫描方式不起作用。

(3)有关@Bean的其他方面

@Configuration
public class AppConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }

}
AppConfig

上面的例子是Spring官方文档提供的,表示如果AccountRepository对象已经通过Spring容器生成,那么@Bean注解可以直接注入进需要的方法参数里。换句话说,同@Autowired等价。

public class Foo {
    public void init() {
        // initialization logic
    }
}

public class Bar {
    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public Foo foo() {
        return new Foo();
    }

    @Bean(destroyMethod = "cleanup")
    public Bar bar() {
        return new Bar();
    }

}
lifecycle callbacks

上面的例子用来说明可以在@Bean注解后面指定初始化方法和清理方法,不过好像用的极少。

另外同XML一样,@Bean提供的对象默认也是单例的。你也可以专门指定生成方式...

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }

}
原型模式

(4)有关@Configuration的其他方面

XML文档提供了一个<import/>标签,使得一篇文档可以包含其他文档。同样在基于Java源码的容器配置中也有一个类似的注解:@Import

@Configuration
public class ConfigA {

     @Bean
    public A a() {
        return new A();
    }

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new B();
    }

}
Import

不过要吐槽一下,这个东西我就没见有人用过。忽略吧骚年,我连例子都是复制文档的。

Spring既然已经支持了基于XML和annotation两种方式的容器配置方法,为什么还要提供基于Java源码的配置方法呢?首先要澄清一个概念,annotation配置和Java源码配置是完全不同方案,使用annotation配置的对象依然需要在XML文档里提供包扫描机制,以及通过ClassPathXmlApplicationContext来初始化容器。而基于Java源码配置则是用过AnnotationConfigApplicationContext初始化容器。至于如何选择,官方文档描述了一个理由:

Combining Java and XML configuration

Spring’s @Configuration class support does not aim to be a 100% complete replacement for Spring XML. Some facilities such as Spring XML namespaces remain an ideal way to configure the container. In cases where XML is convenient or necessary, you have a choice: either instantiate the container in an "XML-centric" way using, for example, ClassPathXmlApplicationContext, or in a "Java-centric" fashion using AnnotationConfigApplicationContext and the @ImportResource annotation to import XML as needed.
Combining Java and XML configuration

很简单不翻译了,反正看你喜欢咯 :-P

五、两个重要的描述

文档中有两个比较重要的描述,我单列在最后。这对于初学Spring框架的用户来说可以忽略。但是如果你想对Spring了解的更深一些还是建议大家去读官方文档的相关部分。

(1)通过XML载入外部资源文件有两种方法

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>
通过Spring提供的对象载入外部资源文件

这也是比较传统的方式。在Spring2.5以后增加了<context:property-placeholder/>标签

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
使用Spring提供的XML标签载入外部资源

显然,第二种方式更加推荐。

(2)BeanFactory or ApplicationContext?

BeanFactory是从容器中获取对象的最上层接口,ApplicationContext扩展了BeanFactory的功能。对此文档中有清楚的描述:BeanFactory主要提供给第三方框架使用,而对于普通用户来说,除非有特殊的理由。否则,你应该直接使用ApplicationContext。

***************************以下是个人感想***************************

有关SpringIoC本来没打算写这么多。只是一点点读文档发现还是应该总结下来,也方便以后自己查询。文档描述的内容基本都在上面了,看不懂英文的同学完全可以对照着上面的内容来编写代码。

原文地址:https://www.cnblogs.com/learnhow/p/5144663.html