Spring Bean的高级装配以及简单实现

Bean的高级装配

一.环境与profile

​ Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大差别,在此过程中要根据环境决定该创建哪个bean和不创建哪个bean,不过Spring并不是在构建时做出这样的决策,而是等到运行时再来确定,,这样的结果就是同一个部署单元(可能会是WAR文件)能够适用于所有的环境,没用必要进行重新构建。

​ Spring3.1版本中引入bean profile功能,使用profile,需要讲所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

​ Java配置中,可使用@Profile注解指定某个bean属于哪一个profile,例如数据源的配置:

package com.huang.configuration;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

import javax.sql.DataSource;

@Configuration
@Profile("daemon")
public class Datasourceconfing {
    @Bean
   public DataSource datasource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/library");
        ds.setUsername("root");
        ds.setPassword("709498");
        return ds;
    }
}

​ 要如何激活Profile呢,spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性spring.profiles.active和spring.profiles,defaut。前面active的优先级大于default的优先级,若都没设置则只会创建没有定义profile中的bean,有多种方式来设置这两个属性:

  • 作为DispatcherServlet的初始化参数;
  • 作为web应用的上下文参数;
  • 作为JNDI条目;
  • 作为环境变量;
  • 作为JVM的系统属性;
  • 在集成测试类上,使用@ActiveProfiles注解设置;

例如在Servlet上下文设置,在Web应用中:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 为servlet激活或设置默认profile以此条件装配bean-->
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>daemon</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
   <!-- 为上下文激活或设置默认profile以此条件装配bean-->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>daemon</param-value>
    </context-param>
</web-app>

使用测试类来判断是否创建和装配成功:

package com.huang;

import com.huang.configuration.Datasourceconfing;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Datasourceconfing.class})
@ActiveProfiles("daemon")
public class test {
    @Autowired
    private Datasourceconfing ds;
    @Test
    public void testdemo(){
        System.out.println(ds);
    }
}

最后输出创建并注入的bean地址:

com.huang.configuration.Datasourceconfing$$EnhancerBySpringCGLIB$$8a313bb0@776aec5c

由上可知,使用测试类注解@ActiveProfiles激活profile没有问题。

二.条件化的Bean

​ 若希望一个或多个bean只有在应用的类路径下包含特定的库时才创建,或者我们希望某个bean只有当另外某个特定的bean也声明之后才会创建,我们还可能也要求某个特定的环境变量设置之后,才会创建某个bean。

​ Spring 4之前很难实现这种级别的配置,但是Spring 4引入新的@Conditional注解,它可以应用到带有@Bean注解的方法上,若给定的条件计算结构为true就会创建这个bean,否则就会忽略这个bean。

​ 有意思的是Spring 4开始@Profile注解也是基于@Conditional和Condition实现。即@Profile本身也使用了@Conditional注解,并且引用ProfileCondition作为Condition实现。ProfileCondition实现Condition接口,并在做出决策的过程中,即matches()方法中考虑到了ConditionContext和AnnotatedTypeMetadata多个因素。

配置类中声明如下:

 package com.huang.configuration;
import com.huang.domain.MagicBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

@Configuration
public class Datasourceconfing {
    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }
}

通过实现Condition接口的matches方法判断环境变量中是否含有magic属性

package com.huang.configuration;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;

public class MagicExistsCondition  implements Condition {
    @Override
    public boolean matches(ConditionContext ct, AnnotatedTypeMetadata at) {
        Environment evn=ct.getEnvironment();
        //检查环境变量中是否含有magic属性
        return  evn.containsProperty("magic");
    }
}

测试:

package com.huang;

import com.huang.configuration.Datasourceconfing;
import com.huang.domain.MagicBean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Datasourceconfing.class})
public class test {
 
    @Autowired
    private MagicBean mb;

    @Test
    public void testdemo(){

        System.out.println(mb);
    }

}

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

​ 使用自动装配会让Spring完全负责将bean引用注入到构造参数和属性中,自动装配很有效,它会减少装配应用程序组件所需要的显示配置的数量。

​ 不过,仅有一个bean匹配所需结果时,自动装配才有效。若不仅只有一个bean匹配结果的话,会阻碍Spring自动装配属性、构造器参数或方法参数。

1.标识首选bean

​ 选择其中一个为首选的(primary)bean能够避免自动装配时的歧义性。使用@Primary注解与@Component组合在组件扫描的bean上,也可在Java配置中使用@Primary和@Bean组合使用为首选的bean。

实例实现类:

//接口
package com.huang.service;
public interface Dessert {
    void shape();
}

//实现类一
package com.huang.service.DessertImpl;
import com.huang.service.Dessert;
public class Cookie implements Dessert {
    @Override
    public void shape() {
        System.out.println("圆的");
    }
}
//实现类二
package com.huang.service.DessertImpl;

import com.huang.service.Dessert;
public class IceCream implements Dessert {
    @Override
    public void shape() {
        System.out.println("锥形的");
    }
}

示例Java配置代码:

package com.huang.configuration;
import com.huang.service.Dessert;
import com.huang.service.DessertImpl.Cookie;
import com.huang.service.DessertImpl.IceCream;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;

@Configuration
@Profile("daemon")
public class Datasourceconfing {

    //首选bean,primary解决歧义
    @Bean
    @Primary
    public Dessert icecream(){

        return new IceCream();
    }
    @Bean
    public Dessert cookie(){

        return new Cookie();
    }


}

可在测试中进行自动装配

package com.huang;

import com.huang.configuration.Datasourceconfing;
import com.huang.domain.MagicBean;
import com.huang.service.Dessert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;

import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Datasourceconfing.class})
@ActiveProfiles("daemon")
public class test {
    //测试自动装配的1歧义性
    @Autowired
    private Dessert dt;

    @Test
    public void testdemo(){

        System.out.println(ds);
        System.out.println(mb);
        //测试输出默认主对象注入
        dt.shape();
    }

}

自动装配会在spring容器中自动选择带有注解primary的bean。

输出显示为:

锥形的

​ 但问题是,标记多个首选的bean时又会产生新的歧义,此时相当于没有首选了,解决歧义性问题而言,限定符是一种更强大的机制。

2.限定自动装配的bean

​ Spring限定符能在所有可选bean的范围内缩小范围,最终只有一个bean符合条件。

​ @Qualifier注解是使用限定符的重要方式,可与@Autowired和@Inject协同使用。

三个Dessert接口的实现类如下:

package com.huang.service.DessertImpl;
import com.huang.service.Dessert;
public class Cookie implements Dessert {
    @Override
    public void shape() {
        System.out.println("圆的");
    }
}

package com.huang.service.DessertImpl;
import com.huang.service.Dessert;
public class IceCream implements Dessert {
    @Override
    public void shape() {
        System.out.println("锥形的");
    }
}

package com.huang.service.DessertImpl;
import com.huang.service.Dessert;
public class Lollipop implements Dessert {
    @Override
    public void shape() {
        System.out.println("棒棒糖型的");
    }
}

详细实体配置代码如下:

package com.huang.configuration;

import com.huang.service.Dessert;
import com.huang.service.DessertImpl.Cookie;
import com.huang.service.DessertImpl.IceCream;
import com.huang.service.DessertImpl.Lollipop;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@Configuration
public class Domainconfing {
    //首选bean,解决歧义
    @Bean
    @Primary
    public Dessert icecream(){

        return new IceCream();
    }
    @Bean
    public Dessert cookie(){

        return new Cookie();
    }
    @Bean
    public Dessert Lollipop(){

        return new Lollipop();
    }

}

相比于@Primary,@Qualifier具有更高的优先级,具体代码及输出如下:

package com.huang;


import com.huang.configuration.Domainconfing;
import com.huang.service.Dessert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Domainconfing.class})
public class test1 {
    @Autowired
    //限定符确定唯一bean
    @Qualifier("Lollipop")
    private Dessert dt;
    @Test
    public void testdemo(){
        //测试输出默认主对象注入
        dt.shape();
    }
}

输出为:

八月 31, 2020 9:10:26 上午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
八月 31, 2020 9:10:26 上午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@47c62251, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@3e6fa38a, org.springframework.test.context.support.DirtiesContextTestExecutionListener@66a3ffec, org.springframework.test.context.transaction.TransactionalTestExecutionListener@77caeb3e, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@1e88b3c, org.springframework.test.context.event.EventPublishingTestExecutionListener@42d80b78]
棒棒糖型的

​ 所有使用@Component注解声明的bean默认的限定符名称为首字母改为小写,使用显式配置则与返回bean的方法名相同。

​ 创建自定义限定符只需要在声明bean时使用@Qualifier即可,即@Qualifier与@Component和@Bean联用,如:

    //Java显式配置中
    @Bean
    @Qualifier("XiaoHuang")
    public Dessert cookie(){
        return new Cookie();
    }

    //使用隐式配置
   @Component
   @Qualifier("XiaoHuang")
   public class Cookie implements Dessert {
    @Override
    public void shape() {
        System.out.println("圆的");
    }
    }

若两个bean具有相同的限定符,通常限定符不是随便取名,而是根据其特性,上述例子中,甜品有冷热之分,对于两个bean都有冷@Qualifier(“cold”)限定,我们可以在其下一级再次使用不同的限定符,例如具体的名称,实例,由于java不允许出现同类型的多个注解,故使用自定义注解代码如下:

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {
}

package com.huang.annotions;

import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cream {
}


package com.huang.annotions;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Sicle {
}

将自定义注解用于显式java配置中,如下:

package com.huang.configuration;
import com.huang.annotions.Cold;
import com.huang.annotions.Cream;
import com.huang.annotions.Sicle;
import com.huang.service.Dessert;
import com.huang.service.DessertImpl.Cookie;
import com.huang.service.DessertImpl.IceCream;
import com.huang.service.DessertImpl.Lollipop;
import com.huang.service.DessertImpl.Popsicle;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class Domainconfing {
    //首选bean,解决歧义
    @Bean
    //使用自定义限定注解
    @Cold
    @Cream
    public Dessert icecream(){

        return new IceCream();
    }
    @Bean
    @Qualifier("XiaoHuang")
    public Dessert cookie(){

        return new Cookie();
    }
    @Bean
    public Dessert lollipop(){

        return new Lollipop();
    }
    @Bean
    //使用自定义注解
    @Cold
    @Sicle
    public Dessert popsicle(){
        return new Popsicle();
    }
}

在注入点使用自定义的限定符注解,测试装配是否成功:

package com.huang;
import com.huang.annotions.Cold;
import com.huang.annotions.Sicle;
import com.huang.configuration.Domainconfing;
import com.huang.service.Dessert;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={Domainconfing.class})
public class test1 {
    @Autowired
    //限定符确定唯一bean
    @Cold
    @Sicle
    private Dessert dt;
    @Test
    public void testdemo(){
        //测试输出默认主对象注入
        dt.shape();
    }
}

输出:

八月 31, 2020 10:23:32 上午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
八月 31, 2020 10:23:32 上午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@42e26948, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@57baeedf, org.springframework.test.context.support.DirtiesContextTestExecutionListener@343f4d3d, org.springframework.test.context.transaction.TransactionalTestExecutionListener@53b32d7, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@5442a311, org.springframework.test.context.event.EventPublishingTestExecutionListener@548e7350]
哈哈,冰棍

​ 在本节和前面章节中,讨论了几种通过自定义注解扩展Spring的方式,为了创建自定义的条件化注解,我们创建·一个性的注解并在这个注解上添加@Conditional。为创建自定义限定符注解,我们创建一个新的注解,并在这个注解上添加了@Qualifier注解。这种技术可以用到很多Spring注解中去,从而形成特定功能的自定义注解。

四.bean的作用域

​ 默认Spring上下文中,bean为单例形式,即每次注入都是同一实例。Spring定义了多种定义域,可基于这些定义域创建bean。

  • 单例(Singleton):在整个应用中,只创建一个bean的实例。
  • 原型(Prototype):每次注入或者通过Spring上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Request):在Web应用中,为每个请求创建一个bean实例。

单例为默认的作用域,要使用其它作用域,要使用@Scope注解,与@Component和@Bean一起使用。

使用隐式@Component和@ComponentScan,创建的bean和配置如下

//weapon接口
package com.huang.service;
public interface Weapon {
    void Operation();
}

//实现一
package com.huang.service.WeaponImpl;
import com.huang.annotions.Weapon.ModernArms;
import com.huang.service.Weapon;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@ModernArms
//定义域为原型,这里可以写为@Scope("prototype"),但以下方式更为安全
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Gun implements Weapon {
    @Override
    public void Operation() {
        System.out.println("射击...");
    }
}


//实现二
package com.huang.service.WeaponImpl;
import com.huang.annotions.Weapon.ColdArms;
import com.huang.service.Weapon;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@ColdArms
//会话和请求作用域
@Scope(.SCOPE_SINGLETON)
public class Spear implements Weapon {
    @Override
    public void Operation() {
        System.out.println("刺...");
    }
}

//配置
package com.huang.configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan("com.huang.service.WeaponImpl")
public class WeaponConfing {
}


使用测试:

package com.huang;
import com.huang.annotions.Weapon.ColdArms;
import com.huang.configuration.WeaponConfing;
import com.huang.service.Weapon;
import junit.framework.TestCase;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=WeaponConfing.class)
public class test2 {
    @Autowired
    @ColdArms
    private Weapon wp;
    @Test
    public void testdemo(){
        //测试输出默认主对象注入
        TestCase.assertNotNull(wp);
        wp.Operation();
    }
}

测试结果:

八月 31, 2020 4:05:34 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getDefaultTestExecutionListenerClassNames
信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
八月 31, 2020 4:05:34 下午 org.springframework.test.context.support.DefaultTestContextBootstrapper getTestExecutionListeners
信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@7d9d1a19, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@39c0f4a, org.springframework.test.context.support.DirtiesContextTestExecutionListener@1794d431, org.springframework.test.context.transaction.TransactionalTestExecutionListener@42e26948, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@57baeedf, org.springframework.test.context.event.EventPublishingTestExecutionListener@343f4d3d]...

​ 对于Web应用来说,对于典型的电子商务来说,将购物车作为一个bean的话,如果采用单例模式,那么所有的用户都共用一个购物车,这显然是不行的,若采用原型,用户在应用的一处加入购物车后,另一场加入后,前面加入的就没用了,相当于又创建了一个性的购物车,数据不能共存。使用WebApplicationContext的会话属性即

SCOPE_SESSION,它会为当前会话创建一个bean,对于整个应用来说会有多个会话即可产生多个bean,但每个单独的bean对于当前会话来说就是单例的。

​ 值得注意的@Scope中还有另外一个属性即proxyMode,简单了解一下应用场景,Spring上下文中的一个单例bean,例如StoreService,有会话作用域ShoppingCourt注入,由于没有当时没有会话登入,Spring并不会将实际的一个ShoppingCourt bean注入,即使用一个该bean的代理接口,当,真真有会话登入时,StoreService调用ShoppingCourt方法时,代理才会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCourt bean。

proxyMode的ScopedProxyMode.INTERFACES表明要代理要实现这个接口,ScopedProxyMode.TARGET_CLASS表明是创建基于类的代理。

在这里插入图片描述

五.运行时注入

​ 依赖注入通常是将一个bean的引用注入到另一个bean的属性或构造器参数中去,通常为对象之间的关联,但也有将值注入到bean的属性或构造器参数中,这些这可采用硬编码的方式,但有时,我们又想让这些值在运行时确定,Spring提供两种方式:

  • 属性占位符。
  • Spring表达式语言(SpEL)。
1.注入外部的值

最简单的方式就是通过声明属性源,并通过Spring的Enviroment来检索属性,具体例子用外部属性来装配bean。

//实体类
package com.huang.domain;
public class BlankDisk {
    private String title;
    private String artist;
    public BlankDisk(String title,String artist) {
        this.title=title;
        this.artist=artist;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public String getArtist() {
        return artist;
    }
    public void setArtist(String artist) {
        this.artist = artist;
    }
}

//Java配置
package com.huang.configuration;
import com.huang.domain.BlankDisk;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
@Configuration
//外部属性文件路径
@PropertySource(value="classpath:/com/huang/properties/MusicList.properties",encoding = "UTF-8")
//可能会有乱码,故再次将文本编码设为utf-8
public class Diskconfing {
   @Autowired
    Environment ev;
    @Bean
    public BlankDisk blankDisk(){
        return new BlankDisk(ev.getProperty("blankdisk.title"),ev.getProperty("blankdisk.artist"));
    }

}

要读取的的properties文件如下:

blankdisk.title=烟火里的尘埃
blankdisk.artist=华晨宇

测试输出Bean的信息如下:

package com.huang;
import com.huang.configuration.Diskconfing;
import com.huang.domain.BlankDisk;
import junit.framework.TestCase;
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;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= Diskconfing.class)
public class test3 {
    @Autowired
    private BlankDisk bl;
    @Test
    public void testdemo(){
        TestCase.assertNotNull(bl);
        System.out.println("歌名:"+bl.getTitle()+"\n歌手:"+bl.getArtist());
    }
}

输出:

歌名:烟火里的尘埃
歌手:华晨宇
2.使用Spring表达式语言进行装配

SpEL特性:

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

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

​ 其基本格式为:#{Expression}

表达式中使用类型:T(),例如:

#{T(java.lang.Math).PI}

​ 基本运算符:
在这里插入图片描述

一些其它运算符:

  • 查询运算符:(.?[]),对集合进行过滤,得到集合的子集合。(.1)和(.$[])分别为查询第一个匹配和最后一个匹配的。
  • 投影运算符:(.![]),从集合选择特定属性的成员放到另一集合中。

六.总结

​ 第二章基本的bean装配之外,我们又学习了强大的高级装配。首先,我们学习了Spring Profile,解决了跨环境的部署问题,运行时,通过将环境相关的bean与当前激活的Profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要重写构建。

​ Spring 4提供一种够味通用的方式,通过这种方式能够声明某些bean的创建是否要依赖于给定条件的输出结果,,即结合使用@Conditional注解和Spring Conditon接口,为开发人员提供了一种强大而灵活的机制,实现条件话的创建bean.。

​ 以及两种解决自动装配歧义的方法,即首选bean和限定符即@Primary和@Qualifier,以及解决某些限定符相同时,多级限定符的自定义限定符解决方案,将范围缩小到只有一个bean符合范围。

​ 大多数bean都是以单例方式创建的,但有时其它创建策略更为合适。Spring能够让bean以单例、原型、会话、请求作用域的方式来创建。声明请求和会话作用域的bean时,还要学会如何创建作用域代理,它分为基于类的代理和基于接口的代理。

​ 最后就是SpEL,可在运行时计算注入到bean中的值。


参考文献:[Spring in action fourth edition] CraigWalls

​ 张卫滨 译


  1. ↩︎

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值