《Spring实战》学习笔记-2

53 阅读10分钟

在Spring中,对象不需要自己创建管理或者去查找和它有协作关系的对象,容器会将需要协作的对象的引用赋予各个对象,创建应用对象的协作关系的行为通常称之为装配,即依赖注入的本质。

Bean的装配

Spring配置的可选方案

spring配置的可选方案主要有三种

  1. 在XML中进行显式配置
  2. 在JAVA中进行显式配置
  3. 隐式的bean发现机制和自动装配

提供三种可选的方案让Spring变得更加灵活,我们可以在一个项目中同时使用这些技术,分别用这些技术对bean进行装配。但是,我们应该尽可能使用自动配置的机制,当我们需要显式配置bean时(比如有些源码不是由我们维护的,但是必须要为这些代码配置bean时),更加推荐使用类型安全并且比XML更加强大的JAVA Config。

bean的自动化装配

Spring从两个角度来实现自动化装配

  • 组件扫描:Spring会自动发现应用上下文中所创建的bean
  • 自动装配:Spring自动满足bean之间的依赖

组件扫描

以一个例子为例: 首先建立CD的概念,并建立CD播放器的概念

// CompactDisc接口
package org.example.springdemo.cd;

public interface CompactDisc {
    void play();
}

然后对CompactDisc接口进行一个实现,以SgtPeppers类为例

package org.example.springdemo.cd;

import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc{
    private String title = "Sgt.Peppers's Lonely Hearts Club Band";
    private String artist = "The Beatles";

    public void play() {
        System.out.println("Playing" + title + " by " + artist);
    }
}

和CompactDisc接口一样,SgtPeppers的具体内容并不重要,需要注意的是SgtPeppers类上使用了Component注解,这个注解表明这个类会作为一个组件类,并告知Spring要为这个类创建一个bean,而不需要显式配置bean,Spring会将这些事情处理妥当。 但是组件扫描默认是不启用的,我们还需要显式配置一下Spring,从而命令它去寻找带有@Component注解的类,并为它创建bean,这里我们可以使用@ComponentScan注解,从而在Spring中启动组件扫描。在我们的项目中使用了SpringBoot,可以看到在启动类中使用了SpringBootApplication注解,这个注解中包含了@ComponentScan注解 image.png 如果没有其它配置的时候,@ComponentScan注解默认会扫描与配置类相同的包,因为启动类位于springdemo类之下,所以会扫描这个包及这个包的子包,查找到所有带有@Component注解的类,这样就可以发现CompactDisc类,并且为其创建一个bean。当然也可以使用XML来启用组件扫描,可以使用Spring context命名空间的<context: component-scan>元素,一个简单的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:Context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

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

接下来创建一个简单的类来进行组件扫描的测试

package org.example.springdemo.cd;
import org.example.springdemo.SpringDemoApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertNotNull;

// @RunWith(SpringJUnit4ClassRunner.class)
// @ContextConfiguration(classes = SpringDemoApplication.class)
@SpringBootTest
public class CDPlayerTest {

    @Autowired
    private CompactDisc cd;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }
}

CDPlayerTest使用了Spring的SpringJUnit4ClassRunner,以便在测试的时候自动创建Spring的应用上下文,注解@ContextConfiguration会告诉它需要在SpringDemoApplication加载配置,因为SpringDemoApplication中包含了@ComponentScan,为了方便我们使用SpringBootTest(我们使用的是SpringBoot框架,单元测试放到了test目录下,手动指定程序的配置文件不太方便),所以最后的应用上下文中应该包括CompactDisc bean。为了证明这一点,测试代码中有一个CompactDisc类型的属性,并且这个属性带有@AutoWired注解,以便于将CompactDisc bean注入到测试代码之中,最后会有一个简单的测试方法断言cd属性不为null。如果它不为null的话,就意味着Spring能够发现CompactDisc类,自动在Spring上下文中将其创建为bean并将其注入到测试代码中。 image.png

bean的命名

Spring应用的上下文中所有的bean都会给定一个ID,在前面的例子中,尽管没有明确地为SgtPeppers bean指定一个ID,但Spring会根据类名为其指定一个ID,具体来说,这个bean的ID是sgtPeppers,如果想要为这个bean设置不同的ID,要做的就是将值传递给@Component注解,或者可以使用JAVA依赖注入规范的@Named注解为bean设置ID。

设置组件扫描的基础包

@ComponentScan默认扫描的基础包是配置类所在的包,如果想要扫描不同的包或者扫描多个基础包的话,可以在@ComponentScan注解的value属性中指明包的名称,如果想要更加清晰地表明目前所设置的是基础包,可以通过basePackages属性进行配置,这个属性可以设置为字符串,也可以设置为字符串数组,这种方式使用字符串类型,可能会存在一定的安全问题,所以我们当然也可以设置一个属性为包中所包含的类或者接口,这个属性是basePackageClasses。

自动装配

在应用程序中,如果所有的对象是独立的,彼此之间没有依赖,就可以像SgtPeppers的bean这样,需要的可能就只是组件的扫描,但是很多对象会依赖其他的对象才能完成任务,这样我们就需要将扫描得到的bean和它们的依赖装配在一起,就需要用到Spring的自动装配。

使用注解来实现自动装配

简单来说,自动装配就是让Spring自动满足bean依赖的一种方法,在满足依赖的过程中,会在Spring上下文中寻找某个bean需求的其它bean,为了声明要进行自动装配,我们可以使用@Autowired注解,使用这个注解在构造器上表明Spring会通过构造器来实例化并传入一个声明的对应类型的bean。@Autowired注解不仅能够用在构造器上,还可以用在属性的setter方法上实现自动装配。总之,Spring在初始化bean之后,会尽可能去满足bean的依赖,如果没有匹配到的bean,在应用上下文创建的时候,Spring会抛出一个异常,为了避免异常的出现,可以将@AutoWired的required设置为false。在将required属性设置为false的时候,Spring会尝试自动装配,如果没有匹配到的bean的话,Spring会将这个bean处于未装配的状态。但是,把required设置为false的时候需要小心,如果不进行null检查就有可能出现NPE问题,而当有多个bean能满足要求的时候,Spring会抛出异常,表明没有明确指定选择哪个bean进行自动装配。@AutoWired是Spring特有的注解,如果不想使用Spring的特性可以使用@Inject注解。@Inject注解来源于JAVA依赖注入规范,@Named也是这个规范提供的,在自动装配中,Spring同时支持@Inject和@Autowired,尽管它们有一些细微的差别,但是它们是可以相互替换的。

验证自动装配

我们用如下的代码来验证:

package org.example.springdemo.cd;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Scanner;

import static org.junit.Assert.assertNotNull;

@RunWith(SpringJUnit4ClassRunner.class)
//@ContextConfiguration(classes = org.example.springdemo.SpringDemoApplication.class)
@SpringBootTest
public class CDPlayerTest {

    @Autowired
    private CompactDisc cd;

    @Autowired
    private SgtPeppers player;

    @Test
    public void cdShouldNotBeNull() {
        assertNotNull(cd);
    }

    @Test
    public void testPlayer()  {
        player.play();

//        assertEquals("Playing Sgt.Peppers's Lonely Hearts Club Band by The Beatles", reader.readLine());
    }
}

image.png

通过JAVA代码来装配bean

尽管在很多情况下,使用组件扫描和自动配置来实现Spring的自动化配置是更为推荐的方式,但有时候自动化配置的方案是行不通的,需要明确配置Spring。比如说当想要将第三方库的组件装配到应用中,这样没有办法在第三方库的组件上加注解。

创建配置类

创建配置类的关键在于在类上加@Configuration注解,表明这个注解是一个配置类,这个类应该包含在Spring上下文中如何创建bean的细节。到此为止,我们都是依赖组件扫描来发现Spring应该创建的bean,尽管我们可以同时使用组件扫描和显式配置,但是本节中我们应该更加关注于显式配置,所以我们将@ComponentScan注解移除掉。

声明简单的bean

要在JavaConfig中声明bean的时候,我们需要编写一个方法,这个方法会创建所需要类型的实例,然后给这个方法添加@Bean注解。如:

@Bean
public CompactDisc sgtPeppers() {
    return new SgtPeppers();
}

@Bean注解告诉Spring这个方法会返回一个对象,这个对象要注册成Spring应用上下文的bean,方法体中包含了最终产生bean实例的逻辑。默认情况下,bean的ID与@Bean注解的方法名是一样的,在本例中,bean的名字将会是sgtPeppers。如果你想为其设置成一个不同的名字的话,可以通过name属性进行设置。

借助JavaConfig实现注入

先前所声明的CompactDisc bean很简单,自身并没有其他的依赖。但是现在我们需要声明CDPlayer bean,它依赖于CompactDisc,在JavaConfig中如何将它们装配起来呢? 在JavaConfig中装配bean的最简单的方式就是引用创建bean的方法,如:

@Bean
public CDPlayer cdPlayer() {
    return new CDPlayer(sgtPeppers())
}

看起来这个CDPlayer是通过调用sgtPeppers()方法得到的,但跟java调用其它方法不同,sgtPeppers()方法加上了@Bean注解,Spring会拦截所有对它的调用,并确保直接返回这个方法创建的bean,而不是每次都对其进行实际的调用,即不是每次都创建了一个新的SgtPeppers实例。在Spring中,每个bean都是单例的。上面的方法是通过调用方法来进行的,当然我们也可以通过另外一种形式:

public CDPlayer cdPlayer(CompactDisc compactDisc) {
    return new CDPlayer(compactDisc)
}

这样当Spring调用CDPlayer()创建CDPlayer bean的时候,它会自动装配一个CompactDisc到配置方法之中,然后方法体就可以按照合适的方法使用它。借助这种技术也可以将CompactDisc注入到CDPlayer的构造器中,并且不需要明确引用CompactDisc的@Bean方法。通过这种方法引用bean往往是最佳的选择,因为不会要求将CompactDisc声明到同一个配置类中,在这里甚至没有要求CompactDisk必须要求在JavaConfig中声明,实际上它可以通过组件扫描的功能自动发现或者通过XML来进行配置,你可以将配置分散到多个配置类、XML文件以及自动扫描和装配到bean中去。即带有@Bean注解的方法可以采用任何必要的Java功能来产生bean实例,构造器和@Setter方法只是@Bean方法的两个简单样例。

使用XML装配Bean

创建XML配置规范

在使用XML为Spring装配bean的时候,需要创建一个新的配置规范,对应于JavaConfig中的创建一个带有@Configuration注解的类,而在XML配置中,这意味着要创建一个XML文件,并且以beans元素作为根。例如: image.png 在使用xml时,需要在配置文件顶部声明多个XML模式文件(XSD),这些文件定义了配置Spring的XML元素,借助Spring Tool Suite能够创建XML配置文件。 用来装配bean的最基本的XML元素包含在spring-beans模式之中,在上面这个XML文件中,它被命名为根命名空间。

使用xml装配比较繁琐,因此我们很少使用,大多使用自动装配和Java Config的方式。