Spring实战 | 第一部分 Spring的核心(第二章 装配bean,第三章 高级装配)

2年前 (2022) 程序员胖胖胖虎阿
263 0 0

第二章 装配bean 

声明bean

构造器注入和setter方法注入

装配bean

控制bean的创建和销毁

一、Spring配置的可选方案

主要的装配机制:

1、在xml中进行显式配置

2、在Java中进行显式配置

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

尽可能的用自动配置的机制。

二、自动化装配bean

1、创建可被发现的bean

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

 组件扫描:spring会自动发现应用上下文中所创建的bean。

自动装配:spring自动满足bean之间的依赖。

组件扫描和自动装配组合在一起就能发挥出强大的威力,它们能够将你的显示装配降到最低。

① 因为使用了@Component注解,所以spring会为你把事情处理妥当。

② @ComponentScan注解启用了组件扫描

③ 通过xml启用组件扫描

2、为组件扫描的bean命名

强烈推荐@Component命名

3、设置组件扫描的基础包

@Configuration
@ComponentScan("soundsystem")
public class CDPlayConfig{
}

设置组件扫描的基础包

@Configuration
@ComponentScan(basePackageClasses={CDPlayer.class,DVDPlayer.class})
public class CDPlayerConfig{
}

4、通过为bean添加注解实现自动装配

@Autowired

将required属性设置为false时,Spring会尝试执行自动装配,但是如果没有匹配的bean的话,spring将会让这个bean处于未装配的状态。但是此时要进行null值检查,否则会出现空指针异常。

@Inject注解来源于Java依赖注入规范

spring同时支持@Autowired和@Inject。

5、验证自动装配

三、通过Java代码装配bean

当要将第三方库中的组件装配到程序中时,这种情况下是没办法加@Component和@Autowired注解的,因此不能自动化装配。

显式配置时,JavaConfig是更好的方案,JavaConfig是配置代码,JavaConfig要放到单独的包中。

1、创建配置类

创建JavaConfig类的关键在于为其添加@Configuration注解,@Configuration注解表明这个类是一个配置类,该类应该包含在Spring应用上下文中如何创建bean的细节。

2、声明简单的bean

3、借助JavaConfig实现注入

@Bean注解表明这个方法会创建一个bean实例并将其注册到Spring应用上下文中。

4、通过xml装配bean

① 创建XML配置规范

② 声明一个简单的bean

③ 借助构造器注入初始化bean

④ 设置属性

推荐构造器注入

bcd4326df08fbd4c9cb3a226ce2d430e315.jpg

5、导入和混合配置

① 在JavaConfig中引用XML配置

② 在XML配置中引用JavaConfig

6、小结

Spring框架的核心是Spring容器。容器负责管理应用中组件的生命周期,它会创建这些组件并保证他们的依赖能够得到满足,这样的话,组件才能完成预定的任务。在本章中,我们看到了在Spring中装配bean的三种主要方式,这些技术描述了Spring应用中的组件以及这些组件之间的关系。

尽可能使用自动化配置,以避免显示配置所带来的维护成本。但是,如果你确实需要显示配置Spring的话,应该优先选择基于Java的配置,它比基于CML的配置更加强大、类型安全并且易于重构。

第三章 高级装配

Spring profile

条件化的bean声明

自动装配与歧义性

bean的作用域

Spring表达式语言

一、环境与profile

1、spring中的profile是什么?

profile可以理解为我们在Spring容器中所定义的Bean的逻辑组名称,只有当这些Profile被激活的时候,才会将Profile中所对应的Bean注册到Spring容器中。举个更具体的例子,我们之前定义bean的时候,当spring容器以启动的时候,就会一股脑的全部加载这些信息完成对bean的创建;而使用profile后,它会将bean的定义进行更细粒度的划分,将这些定义的bean划分为几个不同的组,当spring容器加载信息的时候,首先查找激活的profile,然后只会去加载被激活的组中所定义的bean信息,而不被激活的profile中所定义的bean是不会加载用于创建bean的。

2、为什么要是用profile

因为需要啥就加载啥,不需要的就不用了加载了

3、配置spring profile

在3.1版本中,spring引入了bean profile的功能。要使用profile,首先要将所有不同的bean定义整理到一个或多个profile中,将应用部署到每个环境中,确保对应的profile处于激活(active)的状态。

在java配置中,可以使用@profile注解指定某个bean属于哪一个profile。

4、激活profile

spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果配置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但是如果没有配置spring.profiles.active,那spring将会查找spring.profiles.default的值。

有两种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数
  • 作为web应用的上下文参数
  • 作为环境变量
  • 作为JVM的系统属性
  • 在继承测试类上,使用@ActiveProfiles注解配置。

我所喜欢的一种方式是使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,我会在servlet上下文中进行设置(为了兼顾到ContextLoaderListener)。例如,在web应用中,设置spring.profiles.default的web.xml文件会如下所示:

在web应用的web.xml文件中设置默认的profile

<?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">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/spring/root-context.xml</param-value>
    </context-param>
    <context-param>
        <param-name>spring.profiles.default</param-name><!--为上下文设置默认的profile-->
        <param-value>dev</param-value>
    </context-param>
    <listen>
        <listen-class>
            org.springframework.web.context.ContextLoaderListenr
        </listen-class>
    </listen>
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>spring.profiles.default</param-name><!--为servlet设置默认的profile-->
            <param-value>dev</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>appServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</beans>

按照这种方式设置spring.profiles.default,所有开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境的设置运行代码,而不需要额外的配置。

系统会优先使用spring.profiles.active中所设置的profile。

spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。在集成测试时,通常想要激活的是开发环境的profile。

在条件化创建bean的时候,spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。

二、条件化的bean

spring4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否者,这个bean就会被忽略。

例如,假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。如果环境中没有这个属性,那么MagicBean将会被忽略。

条件化的创建bean:

@Bean
@Conditional(MagicExistsCondition.class)//条件化地创建bean
public MagicBean magicBean(){
    return new MagicBean();
}

可以看到,@Conditional中给定了一个Class,它指明了条件,在本例中,也就是MagicExistsCondition。@Conditional将会通过Condition接口进行条件对比:

public interface Condition{
    boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata);
}

设置给@Conditional的类可以是任意实现了Condition接口的类。可以看出,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建bean。

在condition中检查是否存在magic属性

public class MagicExistsCondition implements Condition{
    public boolean matches(ConditionContext ctxt,AnnoatedTypeMetadata metadata){
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");//检查magic属性
    }
}

在上面的程序中,matches()方法很简单但功能很强大。它通过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。如果返回true,就表示@Conditional注解上引用MagicExistsCondition的bean都会被创建。

ConditionContext是一个接口,大致如下所示:

public interface ConditionContext{
    //检查bean定义
    BeanDefinitionRegistry getRegistry();
    //检查bean是否存在,检查bean的属性
    ConfigurableListableBeanFactory getBeanFactory();
    //检查环境变量是否存在以及它的值是什么
    Environment getEnvironment();
    //读取ResourceLoader加载的资源
    ResourceLoader getResourceLoader();
    //加载并检查类是否存在
    ClassLoader getClassLoader();
}

AnnotatedTypeMetadata则能够让我们检查带有@bean注解的方法上是否还有其他的注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。

public interface AnnotatedTypeMetadata{
    //借助isAnnotated()方法,判断带有@bean注解的方法是否还有其他特定的注解。
    boolean isAnnotated(String annotationType);
    //检查@bean注解的方法上其他注解的属性
    Map<String,Object> getAnnotationAttributes(String annotationType);
    Map<String,Object> getAnnotationAttributes(String annotationType,boolean classValuesAsString);
    MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType);
    MultiValueMap<String,Object> getAllAnnotationAttributes(String annotationType,boolean classValuesAsString);
}

从spring4开始,@profile注解进行了重构,使其基于@Conditional和Condition实现。来看一下,spring4中@profile是如何实现的。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile{
    String[] value();
}

@Profile本身也使用了@Condition注解,并且引用ProfileCondition作为Condition实现。如下所示,ProfileCondition实现了Condition接口,并且考虑到了ConditionContext和AnnotatedTypeMatadata中的多个元素。

ProfileCondition检查某个bean profile是否可用:

class ProfileCondition implements Condition{
    public boolean matches(ConditionContext context,AnnotatedTypeMetadata){
        if(context.getEnvironment()!=null){
            MultiValueMap<String,Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
            if(attrs!=null){
                for(Object value:attrs.get("value")){
                    if(context.getEnvironment().acceptsProfiles(String[] value)){
                        return true;
                    }
                }
                 return false;
            }
        }
        return true;
    }
}

我们可以看到,ProfileCondition通过AnnotatedTypeMetadata得到了用于@Profile注解的所有属性,借助这些信息,它明确的检查value属性,该属性包含了bean的Profile名称,然后,它通过ConditionContext得到的Environment来检查[借助accpetsProfiles()方法]该profile是否处于激活状态。

三、处理自动装配的歧义性

如果不仅有一个bean能够匹配结果的话,这种歧义性会阻碍spring自动装配属性、构造器参数或方法参数。

如果三个实现类均使用了@Component注解,在组件扫描的时候,能够发现他们并将其创建为Spring应用上下文里面的bean,然后,当spring自动装配的时候,它并没有唯一、无歧义的可选值。此时,spring会抛出异常。

就算歧义性是个问题,但实际开发中很少遇见,给定的类型只有一个实现类就好了嘛,自动装配能够很好地运行。

但是,当歧义性确实发生时,spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助spring将可选的bean的范围缩小到只有一个bean。

1、标识首选的bean

在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。

@Bean
@Primary
public class IceCrean implements Dessert{
}

如果XML配置bean的话,同样可以实现此功能。<bean>元素有一个primary属性来指定首选的bean。

2、限定自动装配的bean

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

@Qualifier注解所设置的参数就是想要注入对的bean的ID。所有使用@Component注解声明的类都会创建bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier(“iceCream”)指向的是组件扫描时锁创建的bean,并且这个bean时IceCream类的实例。

@Component--把这些类纳入进spring容器中管理。

创建自定义的限定符

我们可以为bean设置自己的限定符,而不是依赖将beanID作为限定符。在这里需要加上@Qualifier,与@Component配合使用

@Component
@Qualifier("sexy")
public class IceCream implements Dessert{...}

在这种情况下,sexy限定符分配给了IceCreambean。因为它没有耦合类名,因此你可以随意重构IceCream的类名,而不必担心会破坏自动装配。在注入的地方,引用sexy限定符即可:

@Component
@Qualifier("sexy")
public class setDessert(Dessert dessert){
    this.dessert = dessert;
}

 

当配置显式bean的时候,@Qualifier也可以与@bean注解一起使用:

@Bean
@Qualifier("sexy")
public Dessert iceCream{
    return new IceCream();
}

当使用@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随便的名字。

通过声明自定义的限定符注解,我们可以同时使用多个限定符,不会再有Java编译器的限制或错误。与此同时,相对于使用原始的@Qualifier并借助string类型来指定限定符,自定义的注解更为安全。

为了创建自定义的条件化注解,我们创建了一个新的注解并在这个注解上添加了@Conditional。为了创建自定义的限定符注解,我们创建了一个新的注解并在这个注解上添加了@Qualifier。这种技术可以用到很多spring注解中,从而能够将它们组合在一起形成特定目标的自定义注解。

现在我们来看一下如何在不同的作用域中声明bean。

四、bean的作用域

默认情况下,spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其它bean多少次,每次所注入的都是同一个实例。

spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(singleton):在整个应用中,只创建bean的一个实例。
  • 原型(prototype):每次注入或者通过spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(session):在web应用中,为每个会话创建一个bean实例。
  • 请求(request):在web应用中,为每个请求创建一个bean实例。

单例是默认的作用域,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,它可以与@Component或@bean一起使用。

@Component
@Scope(ConfigurableBeanFacory.SCOPE_PROTOTYPE)
public class Notepad{
}

使用ConfigurableBeanFacory类的SCOPE_PROTOTYPE常量设置了原型作用域。你当然也可以使用@Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。

同样也可以使用XML来配置bean:

<bean id="notepad" class="com.oschina.Notepad" scope="prototype"></bean>

不管你使用哪种方式来声明原型作用域,每次注入或从spring应用上下文中检索该bean的时候,都会创建新的实例。这样所导致的结果就是每次操作都能得到自己的notepad实例。

1、使用会话和请求作用域

在web应用中,如果能够实例化在会话和请求范围内共享的bean,那将时非常有价值的事情。例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的,那么将会导致所有的用户都会向同一个购物车中添加商品。另一方面,如果购物车是原型作用域的,那么在应用中某一个地方网购物车中添加物品,在应用的另外一个地方可能就不可用了,因为这里注入的是另一个原型作用域的购物车。

就购物车bean来说,会话作用域是最为合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope注解,它的使用方式与指定原型作用域是相同的:

@Component
@Scope(value=WebApplicationContext.SCOPE_SESSION,
       proxyMode=ScopeProxyMode.INTERFACES)
public ShoppingCart cart(){
}

我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量,这回告诉spring为web应用中每一个会话创建一个ShoppingCart。这会创建多个ShoppingCart的bean实例,但是对于给定的会话只是创建了一个实例,在当前会话中,这个bean相当于单例的。

要注意的是,@Scope同时还有一个proxyMode属性,它被设置成了ScopeProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性之前,我们先来看一下ProxyMode所解决的场景。

假设我们将shoppingCart bean注入到单例StoreService bean的setter方法中,如下所示:

@Component
public class StoreService{
    @Autowired
    public void setShoppingCart(ShoppingCart shoppingCart){
        this.shoppingCart=shoppingCart;
    }
    ...
}

@Autowired是用在Javabean中的注解,通过byType形式,用来给指定的字段或方法注入所需的外部资源。

先看一下bean实例化和@Autowired装配过程:

1、一切都是从bean工厂的getBean方法开始的,一旦该方法调用总会返回一个bean实例 。

2、实例化和装配过程中会多次递归调用getBean方法来解决类之间的依赖。

3、@Autowired是根据类型来查找和装配的,但是我们设置了<beans default-autowired="byName"/>后会影响最终的类型匹配查找。因为在前面有根据BeanDefinition的autowire类型设置PropertyValue值的一步,其中会有新实例的创建和注册。就是那个autowireByName方法

因为StoreService是一个单例bean,会在spring应用上下文加载的时候创建,当它创建的时候,spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。 但ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。

另外,系统中将会有多个ShoppingCart实例,每个用户一个,我们并不想让spring注入某个固定的ShoppingCart实例到storeService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

7d7b3996211b8947c34134969ed6a3e11e6.jpg

2、在xml中声明作用域代理

如果你需要使用XML来声明会话或请求作用域的bean,那么就不能使用@Scope注解及其ProxyMode属性了。<bean>元素的scope属性能够设置bean的作用域,但是该怎样指定代理模式呢?

要设置代理模式,我们需要使用Spring aop命名空间的一个新元素:

<bean id="cart" class="com.oschina.ShoppingCart" scope="session">
    <aop:scoped-proxy/>
</bean>

<aop:scoped-proxy/>是与@Scope注解的ProxyMode属性功能相同的Spring XML配置元素。它会告诉spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

<bean id="cart" class="com.oschina.ShoppingCart" scope="session">
    <aop:scoped-proxy proxy-target-class="false" />
</bean>

为了使用<aop:scoped-proxy>元素,我们必须在XML配置中声明spring的aop命名空间:

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
...
</beans>

在第4章中,我们使用spring和切面编程的时候,会讨论spring aop命名空间的更多知识,现在我们来看一下spring高级配置的另外一个可选方案:spring表达式语言(spring expression language)。

五、运行时值注入

1、注入外部的值

在Spring中,处理外部值最贱的方式就是声明属性源并通过spring的environment来检索属性。

package com.oschina
@Configuration
//声明属性源
@PropertySource("classpath:/com/oschina/app.properties")
public class ExpressiveConfig{
    @Autowired
    Environment env;
    @Bean
    public BlankDisc disc(){
        //检索属性值
        return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));
    }
}

深入学习spring的environment

直接从environment中检索属性是非常方便的,尤其是在Java配置中装配bean的时候。但是spring也提供了通过占位符装配属性的方法,这些占位符的值会来源于一个属性源。

解析占位符

在spring装配中,占位符的形式为使用“${...}”包装的属性名称。

<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist} />"

2、使用spring表达式语言进行装配

spring3引入了spring表达式语言(spring expression language ,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中使用的表达式会在运行时计算的得到值,使用SpEL,你可以实现超乎想象的装配效果,这是使用其他的装配技术难以做到的。

SpEL拥有很多特性,包括:

  • 使用bean的ID来引用bean
  • 调用方法和访问对象的属性
  • 对值进行算术、关系和逻辑运算
  • 正则表达式匹配
  • 集合操作

SpEL能够用在依赖注入以外的其他地方,例如,spring Security支持使用SpEL表达式定义安全限制规则。另外,如果你在spring MVC应用中使用Thymeleaf模板作为视图的话,那么这些模板可以使用SpEL表达式引用模板数据。

SpEL样例

SpEL是一种非常灵活的表达式语言,所以本书中不可能面面俱到的介绍它的各种用法。但是我们可以展示几个基本的例子,这些例子会激发你的灵感,有助于编写自己的表达式。

SpEL表达式使用"#{...}",这与属性占位符有些类似,属性占位符是"${...}"。

除去"#{...}"标记之后,剩下的就是SpEL表达式体了,也就是一个数字常量。这个表达式的计算结果就是数字1,这恐怕不会让你感到丝毫惊讶。

#{T(System).currentTimeMillis()}

它的最终结果是计算表达式的那一刻的当前时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法。

SpEL表达式也可以应用其他的bean或其他bean的属性。例如,如下的表达式会计算得到ID为sgtPeppers的bean的artist属性:

#{sgtPeppers.artist}

我们还可以通过systemProperties对象引用系统属性:

#{systemProperties['disc.title']}

这只是SpEL的几个基础样例。

如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可以使用@Value注解,这与之前看到的属性占位符非常相似。不过,在这里我们所使用的不是占位符表达式,而是SpEL表达式。例如,下面的样例展示了BlankDisc,它会从系统属性中获取专辑名称和艺术家的名字:

public BlankDisc(@Value{'#systemProperties['disc.title']'} string title,
    @Value{'#systemProperties['disc.artist']'} string artist){
    this.title = title;
    this.artist=artist;
}

在XML配置中,你可以将SpEL表达式传入<property>或<constructor-arg>的value属性中,或者将其作为p-命名空间或c-命名空间条目的值。例如,在如下BlankDisc bean的XML声明中,构造器参数就是通过SpEL表达式设置的:

<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:title="#systemProperties['disc.title']"
c:_artist="#[systemProperties['disc.artist']]">

现在我们来学一下SpEL所支持的基础表达式:

表示字面值

实际上可以用来表示浮点值、string值以及Boolean值。

下面的SpEL表达式样例所表示的就是浮点数:

#{3.141592657}

....

SpEL所能做到的另外一件基础的事情就是通过ID引用其他的bean。例如,你可以使用SpEL将一个bean装配到另一个bean的属性中,此时要是用bean ID作为SpEL表达式

SpEL运算符

4b3605ea4c55487c593ab1a595eaad80da9.jpg

例子:#{2*T(java.lang.Math).PI*circle.radius}

这不仅是使用SpEL中乘法运算符的绝佳样例,它也为你展示了如何将简单的表达式组合为更为复杂得表达式。
SpEL还提供了三元运算符

六、小结

我们在本章介绍了许多背景知识,在第二章所介绍的基本bean装配基础之上,又学习了一些强大的高级装配技巧。

首先,我们学习了spring profile,它解决了spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。

Profile bean是在运行时条件化创建bean的一种方式,但是spring4提供了一种更为通用的方式,通过这种方式能能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件化的创建bean。

我们还看了两种解决自动装配歧义性的方法:首选bean以及限定符。尽量将某个bean设置为首选bean是很简单的,单这种方式也有其局限性,所以我们讨论了如何将一组可选的自动装配bean,借助限定符将其范围缩小到只有一个符合条件的bean。除吃之外,我们还看到了如何创建自定义的限定符注解,这些限定符描述了bean的特性。

尽管大多数的spring bean都是单例的方式创建的,但有的时候其它的创建策略更为合适。spring能够让bean以单例、原型、请求作用域或会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,我们还学习了如何创建作用域代理,它分为基于类的代理和基于接口的代理两种方式。

最后我们还学习了spring表达式语言,他能够在运行时计算要注入到bean属性中的值。

对于bean装配,我们已经掌握了扎实的基础知识,现在我们将注意力转向面向切面编程(AOP)。依赖注入能够将组建及其协作的其他组件解耦,与之类似,AOP有助于将应用组件与跨多个组件的任务进行解耦。在下一章,我们将学习在spring中如何创建和使用切面。

相关文章

暂无评论

暂无评论...