Note/Spring实战/4

上一篇:Note3

前言:在 Note1 的 4.3 中简单的描述了什么是装配,以及为什么需要装配。在这一节将详细解析 Spring 是如何装配 Bean 的。创建应用对象之间协作关系的行为通常称为装配(wiring) , 这也是依赖注入(DI) 的本质。

Spring容器会负责创建应用程序中的bean并通过DI来协调这些对象之间的关系。 但是,我们首先需要告诉Spring要创建哪些bean并且如何将其装配在一起。 

Spring 中装配 bean 的三种常见方式

Spring有三种常见的装配方式:

1. 在XML中进行显式配置(XML)

2. 在Java中进行显式配置(JavaConfig)

3. 隐式的bean发现机制和自动装配

尽管可以任意选择或搭配使用这三种方式,但还是建议优先使用自动装配机制,其次可以选择使用JavaConfig,最后才是XML的方式。

 

1. 隐式的bean发现机制和自动装配

1.1 先使用注解声明Bean

@Component
public calss Dog{
	public void say(){
		System.out.println("I'm a dog");
	}
}

Bean声明注解:

@Component 、@Controller、@Service、@Repositpry、(SpringBoot还有一个 @RestController 注解,这里先不提)

这些注解的作用是一样的,只是为了区分代码中的结构分层(Controller/Service/Dao)而做出的区分,@Component注解则通常用作通用的地方,这四个注解所在的代码分层即使混用也没关系(但不推荐)。

Bean声明注解表明该类会作为组件类, 并告知Spring要为这个类创建bean(但不是在添加完注解后马上就创建)。

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

> 组件扫描(component scanning) : Spring会自动发现应用上下文中所创建的bean。

> 自动装配(autowiring) : Spring自动满足bean之间的依赖。 

Spring的组件自动扫描默认是不启用的,可以通过两种方式(JavaConfig 或 XML)来启用组件自动扫描。

 【###】 XML的方式

//test.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/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:compontent-scan base-package="com.test">
    <!--
        1.多个包以逗号分开:base-package="com.test,com.test2,..."
        2.可以使用通配符:base-package="com.test.*"
    -->
</beans>

按照上述配置后,默认会扫描 base-package 属性所指定的包及所有子包中带有@Component注解的类,并在Spring中自动为这些类创建一个bean。

【###】JavaConfig的方式

@Configuration
@ComponentScan
public calss TestConfig{
}

//也可以设置扫描指定的包:
//@ComponentScan("com.test")
//@ComponentScan( basePackages = {"com.test" , "..."} )

如果没有其他配置的话, @ComponentScan默认会扫描当前配置类所在的包及所有子包中带有@Component注解的类,并在Spring中自动为这些类创建一个bean。

1.3 不通过指定包名来扫描

上面这两种方式可以实现扫描指定包,但如果代码重构时,上层包结构可能会改变,这样写死包名就会出问题。所以,@ComponentScan还提供了另一种方法:

> 先在包中创建一个空标记接口(或类)(例如:MarkerTest),

> 使用 basePackageClasses 属性 @ComponentScan( basePackageClasses = {MarkerTest.class , ...} ) 标记接口(或类)所在的包将会作为组件扫描的基础包,基础包及其下的所有子包都会被扫描。

1.4 创建一个 JUnit 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = TestConfig.class)
public class MyJunitTest{
	@Autowired
	private Dog dog;

	@Test
	public void testAaIfNull(){
		assertNotNull(dog);
	}
}

Spring 的 SpringJUnit4ClassRunner 会在测试开始的时候自动创建Spring的应用上下文。 注解@ContextConfiguration会告诉它需要在 TestConfig 中加载配置。 因为 TestConfig 类中包含了@ComponentScan, 因此最终的应用上下文中会包含 dog。

为了测试上述结论是否正确,在类中使用 @Autowired 注入了该 bean,并且使用了一个简单的测试方法断言 dog 不为null。 

1.5 为组件扫描的bean命名

 Spring应用上下文中所有的bean都会给定一个ID,默认将类名的首字母小写作为Bean的ID。当然,也可以自定义Bean的 ID 的值,例如:

@Component("litterDog")
public calss Dog{
	public void say(){
		System.out.println("I'm a dog");
	}
}

还有另外一种方式指定 ID 的值 —— 使用Java依赖注入规范(Java Dependency Injection) 中所提供的@Named注解:

@Named("litterDog")
public calss Dog{
	public void say(){
		System.out.println("I'm a dog");
	}
}

虽然在大多数场景中, @Named 和 @Component 是可以互相替换,但还是推荐使用 @Component 注解。

1.6 Bean的自动装配

自动装配就是让Spring自动满足bean依赖的一种方法, 在满足依赖的过程中, 会在Spring应用上下文中寻找匹配某个bean需求的其他bean。 
使用 @Resource 或 @Autowired 注解来自动满足依赖,详见@Autowired 和 @Resource 注解

【###】@Inject 注解

如果不想使用Spring注解,也可以用 @Inject 注解来替换。@Inject注解来源于Java依赖注入规范,@Inject注解和@Autowired注解之间有一些细微的差别,但大多数场景下还是可以相互替换的。

 

2. 使用 JavaConfig 进行显式配置

尽管推荐使用组件扫描和自动装配来实现自动化配置,但有时候这种方案是行不通的。例如,想要将第三方库的组件装配到自己的应用中,在这种情况下是没有办法在类上添加 @Component 等注解。这是就需要显示配置了,显示配置有两种:Java和XML,先来看看JavaConfig显示配置。

2.1 POJO没有依赖其他POJO的情况

普通Java类

public calss Dog{
    public void say(){
        System.out.println("I'm a dog");
    }
}

JavaConfig显示装配

@Configuration
public calss TestConfig{
    //所装配Bean的ID为和方法名相同
    @Bean
    public Dog dog(){
        return new Dog();
    }

    //如果想要指定Bean的ID,可以指定 name 属性值
    @Bean(name="litterDog")
    public Dog dog(){
        return new Dog();
    }
}

2.2 POJO依赖其他POJO的情况

【###】方式一

普通Java类

public calss Human{
    private Dog dog;

    public Human(Dog dog){
        this.dog = dog;
    }
    
    public void play(){
        dog.say();
    }
}

JavaConfig显示装配

@Configuration
public calss TestConfig{
    @Bean
    public Human human(){
        return new Human(dog());
    }

    @Bean
    public Dog dog(){
        return new Dog();
    }
}

并不是每次调用 dog() 都返回一个新的Bean实例。因为默认情况下,Spring是单例的。所以,Spring会拦截 dog() 的调用并确保返回的是 Spring 所创建的 Bean。所以,无论调用多少次 dog(),返回的都是同一个 Dog 实例。

【###】方式二

针对 POJO 依赖其他 POJO 的情况下,还有另外一种配置方式。

@Configuration
public calss TestConfig{
    @Bean
    public Human human(Dog dog){
        return new Human(dog);
    }

    @Bean
    public Dog dog(){
        return new Dog();
    }
}

这种方式相对于方式一来说是一个更好的选择,因为他不会要求将 Dog 和 Human 声明到同一个配置类中。你可以将 Dog 放在其他 JavaConfig、XML配置或自动扫描装配Bean,都可以。

 

3. 使用 XML 进行显式配置

在此再强调一遍,只有到了万不得已的时候才推荐使用XML进行配置。所以,在这一节学习XML配置是为了能理解并维护项目中已有的XML配置,而不是为了在新的工作任务中使用XML配置。

3.1 声明一个简单的<bean>

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

	<!-- ID为:com.test.XmlTest#0 -->
	<bean class="com.test.XmlTest">

	<!-- ID为:com.test.XmlTest#1 -->
	<bean class="com.test.XmlTest">

	<!-- ID为:xmlTest -->
	<bean id="xmlTest" class="com.test.XmlTest">
</beans>

如果在<bean>中没有指定id,则默认为:全限定类名 + #n。

3.2.1 构造器注入之(注入bean引用)

有两种方案:

方案一:<constructor-arg>元素

方案二:使用 Spring3.0 引入的 c- 命名空间

两者的区别很大程序上在于是否冗长烦琐,<constructor-arg>元素比 c- 会更加冗长,但有些 <constructor-arg>元素可以实现的事情是 c- 无法实现的。

【###】方案一(此处省略xml声明,和上面相同)

<bean id="human" class="com.test.Human">
    <constructor-arg ref="hand"/>
    <constructor-arg ref="eye"/>
</bean>

<bean id="hand" class="com.test.Hand"/>
<bean id="hand" class="com.test.Eye"/>

【###】方案二

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:c="http://www.springframework.org/schema/c"
       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="human" class="com.test.Human" c:hand-ref="hand" c:eye-ref="eye"/>
	<bean id="hand" class="com.test.Hand"/>
	<bean id="eye" class="com.test.Eye"/>
</beans>

解释(c:hand-ref):

[c:]  表示命名空间前缀

[hand]  表示构造器参数名

[-ref]  表示命名约定,它会告诉Spring,后面的值是一个Bean的ID引用,而不是一个字面量“hand”

【###】如果觉得直接写构造器参数名不好,可以按照下面这样进行替换:

<bean id="human" class="com.test.Human" c:_0-ref="hand"/>
<bean id="human" class="com.test.Human" c:_1-ref="eye"/>

<!-- 如果构造器只有一个参数,则可以直接省略参数索引 -->
<bean id="human" class="com.test.Human" c:_-ref="hand"/>

将构造器参数名换成了“0”,也就是参数的索引。因为在XML中不允许数字作为属性的第一个字符,因此必须要添加一个下划线作为前缀。

3.2.2 构造器注入之(注入字面量)

【说明】在下面的字面量注入示例中,字面量都是硬编码的。这里使用硬编码是为了方便,在 Note5 中会讲如何使用属性占位符或Spring表达式语言来实现在运行时动态注入字面量的值。

public class Cat{
    private String name;

    public Cat(String name){
        this.name = name;
    }
}

【###】方案一

<bean id="cat" class="com.test.Cat">
    <constructor-arg value="hello kitty"/>
</bean>

【###】方案二

<bean id="cat" class="com.test.Cat" c:_name="hello kitty"/>

<!-- 或 -->
<bean id="cat" class="com.test.Cat" c:_0="hello kitty"/>

<!-- 如果构造器只有一个参数 -->
<bean id="cat" class="com.test.Cat" c:_="hello kitty"/>

对比于注入Bean的ID引用,去掉了 “-ref” 后缀。

3.2.3 构造器注入之(注入集合)

注入集合到构造器中只能使用 <constructor-arg> 元素,因为 c- 命名空间没有这个功能。

public class Pig{
    private List<Stirng> sons;

    public Pig(List<String> sons){
        this.sons = sons;
    }
}

【###】给集合一个null值

<bean id="pig" class="com.test.Pig">
    <constructor-arg><null/></constructor-arg>
</bean>

【###】给集合赋非空值

<bean id="pig" class="com.test.Pig">
    <constructor-arg>
        <list>
            <value>balckPig</value>
            <value>pinkPig</value>
            <value>whitePig</value>
        </list>
    </constructor-arg>
</bean>

同理,如果集合不是字面量结合而是 bean 引用集合:

<bean id="pig" class="com.test.Pig">
    <constructor-arg>
        <list>
            <ref bean="blackPig"/>
            <ref bean="pinkPig"/>
            <ref bean="whitePig"/>
        </list>
    </constructor-arg>
</bean>

如果构造器参数类型不是List,而是Set。则直接将 <list> 换成 <set> 就可以了,其他不变。

3.3 属性注入

上面的XML注入示例(Bean引用、字面量、集合)都是通过构造器来注入的,没有使用属性的 Setter 方法,接下来就看看如何使用XML实现属性注入。

public class Human{
    private Hand hand;
    
    public void setHand(Hand hand){
        this.hand = hand;
    }
}

3.3.1 属性注入之(注入bean引用)

【###】方案一

<bean id="human" class="com.test.Human">
    <property name="hand" ref="hand"/>
</bean>

<bean id="hand" class="com.test.Hand"/>

<property>元素为属性的Setter方法提供的功能与<constructor-arg>元素为构造器所提供的功能是一样的。

【###】方案二

Spring 为 <constructor-arg> 元素提供了 c- 命名空间作为替代方案。类似的,Spring 也为 <property> 提供了替代方案:p- 命名空间。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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="human" class="com.test.Human" p:hand-ref="hand"/>
	<bean id="hand" class="com.test.Hand"/>
</beans>

解释(p:hand-ref):

[p:]  表示命名空间前缀

[hand]  表示属性名

[-ref]  表示命名约定,它会告诉Spring,后面的值是一个Bean的ID引用,而不是一个字面量“hand”

3.3.2 属性注入之(注入字面量)

【说明】在下面的字面量注入示例中,字面量都是硬编码的。这里使用硬编码是为了方便,在 Note5 中会讲如何使用属性占位符或Spring表达式语言来实现在运行时动态注入字面量的值。

public class Cat{
    private String name;

    public void setName(String name){
        this.name = name;
    }
}

【###】方案一

<bean id="cat" class="com.test.Cat">
    <property name="name" value="hello kitty"/>
</bean>

【###】方案二

<bean id="cat" class="com.test.Cat" p:name="hello kitty"/>

对比于注入Bean的ID引用,去掉了 “-ref” 后缀。

3.3.3 属性注入之(注入集合)

不能使用 p- 命名空间来装配集合,但可以还是用Spring 的 util- 命名空间来进行简化。

public class Pig{
    private List<Stirng> sons;

    public void setSons(List<String> sons){
        this.sons = sons;
    }
}

【###】方案一

<bean id="pig" class="com.test.Pig">
    <property name="sons">
        <list>
            <value>balckPig</value>
            <value>pinkPig</value>
            <value>whitePig</value>
        </list>
    </property>
</bean>

同理,如果集合不是字面量结合而是 bean 引用集合:

<bean id="pig" class="com.test.Pig">
    <property name="sons">
        <list>
            <ref bean="blackPig"/>
            <ref bean="pinkPig"/>
            <ref bean="whitePig"/>
        </list>
    </property>
</bean>

如果属性的参数类型不是List,而是Set。则直接将 <list> 换成 <set> 就可以了,其他不变。

【###】方案二(使用 util- 命名空间简化)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:util="http://www.springframework.org/schema/util"
       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/util
            http://www.springframework.org/schema/util/spring-util.xsd">

    <util:list id="sonList">
        <value>blackPig</value>
        <value>pinkPig</value>
        <value>whitePig</value>
    </util:list>

    <bean id="com.test.Pig" p:sons-ref="sonList"/>
	
</beans>

3.3.4 util- 命名空间中的元素

util:list:创建一个 java.util.List 类型的 bean,其中包含值或引用

util:set:创建一个 java.util.Set 类型的 bean,其中包含值或引用

util:map:创建一个 java.util.Map 类型的 bean,其中包含值或引用

util:properties:创建一个 java.util.Properties 类型的 bean

util:property-path:引用一个 bean 的属性(或内嵌属性),并将其暴露为 bean

<util:constant>:引用某个类型的 public static 域,并将其暴露为 bean

3.4 使用构造器注入还是属性注入?

一个通用的规则是:对于强依赖使用构造器注入,对于可选依赖使用属性注入。

什么是强依赖?例如 Human 对 Hand (即人对于手)即使强依赖关系,而人对鞋子就是可选依赖。

 

4. 导入和混合配置

在 Spring 应用中,组件扫描、JavaConfig 和 XM 配置三者可以混合使用。且在自动装配Bean时,并不在意Bean来自哪里,不管是它是在XML或JavaConfig中声明的还是通过自动扫描得到的。

4.1 组合多个JavaConfig

定义了两个JavaConfig

//CatConfig.java
@Configuration
public class CatConfig{
    @Bean
    public Cat cat(){
        return new Cat();
    }
}

//DogConfig.java
@Configuration
public class DogConfig{
    @Bean
    public Dog dog(){
        return new Dog();
    }
}

将这两个 JavaConfig 组合在一起

@Configuration
@Import(CatConfig.class)
public class DogConfig{
    @Bean
    public Dog dog(){
        return new Dog();
    }
}

//或

@Configuration
@Import({CatConfig.class,DogConfig.class})
public class AnimalConfig{
}

4.2 组合 JavaConfig 和 XML配置

定义一个 JavaConfig

@Configuration
public class DogConfig{
    @Bean
    public Dog dog(){
        return new Dog();
    }
}

定义一个 XML 配置

//cat-config.xml
...
<bean id="cat" class="com.test.Cat" p:name="hello kitty"/>
...

将两个配置组合在一起

@Configuration
@Import(DogConfig.class)
@ImportResource("classpath:cat-config.xml")
public class AnimalConfig{
}

4.3 组合多个 XML 配置

定义两个 XML 配置

//cat-config.xml
...
<bean id="cat" class="com.test.Cat" p:name="hello kitty"/>
...

//dog-config.xml
...
<bean id="dog" class="com.test.Dog" p:name="hello Tony"/>
...

将两个配置组合在一起

//dog-config.xml
...
<bean id="dog" class="com.test.Dog" p:name="hello Tony"/>

<import resource="dog-config.xml"/>
...

4.4 最后

不管使用 JavaConfig 还是 XML 进行配置,我们通常都会创建一个根配置(JavaConfig 或 XML)。在根配置中将多个 JavaConfig 和 XML 配置组合在一起。最后,通常会在根配置中开启应用组件扫描(通过 <context:component-scan> 或 @ComponentScan)。

 

下一篇:Note5

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值