SpringAction第三章

第三章 高级装配

3.1 环境与profile 3.1.1配置profile bean

在我们开发的时候可能会使用相对简单的环境,但是在生产时,因客户端的差异性,我们需要使用另外一种环境,若是重构环境,可能会发生不必要的bug以及问题,Spring中提供了解决办法@Profile

在spring3.1以前@Profile只能适用在类上面,现在此标签可适用于方法上

在JavaConfig中配置

@Configuration
public class DevelopmentProfileConfig {
    
    /**
     * 此方法会搭建一个Hypersonic的嵌入式数据库,模式(schema)定义在schema.sql中
     * ,测试数据通过test-data.sql加载的,此模式适合开发环境
     * @return
     */
    @Bean(destroyMethod="shutdown")
    @Profile("dev")
    public DataSource embeddedDataSource() {
        return new EmbeddedDatabaseBuilder().addScript("classpath:schema.sql")
                .addScript("classpath:test-data.sql")
                .build();
    }
    /**
     * 此环境适用于生产环境
     * @return
     */
    @Bean
    @Profile("prod")
    public DataSource jndiDataSource() {
        JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return  (DataSource) jndiObjectFactoryBean.getObject();
    }
    
}

只有在规定的profile激活时,相应的bean才会被创建,未被profile标记的bean默认都是会被创建的

在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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    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-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd"
       
       
        profile="dev">
  
  
        <!-- 中间代码省略 -->
        </beans>

在底部profile中设置了此xml的环境,其余环境同理

也可以使用同一个xml来定义

<beans profile="dev">
        <bean id="" class=""></bean>
        <bean id="" class=""></bean>
    </beans>
​
    <!-- 中间代码与之前的bean注入没有什么区别 -->
    <beans profile="prod">
        <bean id="" class=""></bean>
        <bean id="" class=""></bean>
    </beans>

 

3.1.2激活profile

激活profile,需要依赖两个独立属性,spring.profiles.defaultspring.profiles.active

spring.profiles.default:默认的环境值 spring.profiles.active激活指定的环境

若是两者都没有设定,则只会创建没有定义在profile中的bean

设置这两种属性的方法

  • 作为DispatcherServlet的初始化参数

  • 作为Web应用的上下文参数

  • 作为JNDI条目

  • 作为环境变量

  • 作为JVM的系统属性

  • 在继承测试类上,使用@ActiveProfiles注解设置

 

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

    <!-- 为上下文设置默认的profile -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>
<servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath*:spring/springmvc-context.xml</param-value>
        </init-param>
        <!-- 为Servlet设置默认的profile -->
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>dev</param-value>
        </init-param>
    </servlet>

相对而言我更加喜欢JavaConfig

@Configuration
@ActiveProfiles("dev")
public class DevelopmentProfileConfi {
       //中间省略
}

注意:在上面的**spring.profiles.defaultspring.profiles.active**两个属性中,profiles是复数的,所以我们可以同时激活多个profiles

 

3.2条件化的bean

spring中提供了一个或多个bean在某种特定的条件下才会被创建的标签,这是在spring4后面引入的

@Conditional标签

此次例子我们希望环境时magic环境时才会创建的bean

我们需要创建一个实现Conditiion接口的类

public class MagicExistsCondition implements Condition{
​
    /**
     * 实现matches方法,若方法返回true,则@Condition所在类会被创建,反之亦然
     */
    @Override
    public boolean matches(ConditionContext ctxt, AnnotatedTypeMetadata metadat) {
        // TODO Auto-generated method stub
        return false;
    }
​
}

此中有两个参数

ConditionContext ctxt: ConditionContext是一个接口

 

public interface ConditionContext {
//返回的BeanDefinitionRegistry检查bean定义
BeanDefinitionRegistry getRegistry();
​
//返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性
ConfigurableListableBeanFactory getBeanFactory();
​
//返回的Environment检查环境变量是否存在以及他的值是什么
Environment getEnvironment();
​
//返回的ResourceLoadert所加载的资源
ResourceLoader getResourceLoader();
​
//返回的ClassLoader加载并检查类是否存在
ClassLoader getClassLoader();
}

AnnotatedTypeMetadata metedate: AnnotatedTypeMetadata也是一个接口

public interface AnnotatedTypeMetadata {
​
    //能够判断带有anntotaionname注解
    boolean isAnnotated(String annotationName);
​
    //通过annotationName获取属性的值,以key-value形式返回
    Map<String, Object> getAnnotationAttributes(String annotationName);
​
    //自行解决~ 看源码
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
​
    //检索给定类型的所有注释的所有属性
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
​
    //自行解决~ 看源码
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
​
}
​

JavaConfig中

    
    @Bean
    @Conditional(MagicExistsCondition.class)//MagicExistsCondition中的matches()返回true则创建
    public MagicBean magincBean() {
        return new MagicBean();
    }

使用方法就如上了, 现在我们来看下@Profile中的源码,因为可以发现@Profile也是一个条件标签,即很有可能实现了@Conditional标签

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
​
    /**
     * The set of profiles for which the annotated component should be registered.
     */
    String[] value();
​
}

@Profile引用了ProfileCondition作为Condition实现,ProfileCondition实现了Condtion接口,并且做出决策考虑到了AnnotatedTypeMetadata 和ConditionContext 中的因素

class ProfileCondition implements Condition {
​
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        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;
    }
​
}

 

处理自动装配的歧义性

这里有三个实现类同一接口

@Component
public class IceCream implements Dessert{
​
}
@Component
public class Cookies implements Dessert{
​
}
@Component
public class Cake implements Dessert{
    
}
//JavaConfig类,当我们想设置dessert值得时候会产生异常
@Configuration
public class JavaConfig {
    
    private Dessert dessert;
​
    @Autowired
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }
    
}

在JavaConfig中,这种情况Spring无法做出选择,会抛出NoUniqueBeanDefinitionException异常,这种情况我们有三种解决方案

3.3.1标示首选得bean(第一种)

在实现类中贴上标签@Primary,此标签设置了Spring在设值时首选使用IceCream实现类

@Component
@Primary
public class IceCream implements Dessert{
​
}

在xml中配置

<!-- 将primary设置成为true值 -->
<bean id="ice" class="com.myapp.IceCream" primary="true"></bean>

3.3.2 限定自动装配的bean

在上述中的setter方法上加上@Qualifier标签

    @Autowired
    @Qualifier("iceCream")//此标签说明了引入IOC容器中id为iceCream的bean
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }

使用此标签所引用的bean要具有类型的"iceCream"作为限定符,若是没有,则会默认以方法名指定

 

我们也可以自定义的创建限定符

@Component
@Qualifier("cold")
public class IceCream implements Dessert{
​
}
@Configuration
public class JavaConfig {
    
    private Dessert dessert;
​
    @Autowired
    @Qualifier("cold")
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }
    
}

这种方法就是往类上声明一个id名

 

但是有种情况,SPringl又会懵逼

@Component
@Qualifier("cold")
public class Popsicle implements Dessert{
​
}
​
@Component
@Qualifier("cold")
public class IceCream implements Dessert{
​
}
​
@Configuration
public class JavaConfig {
    
    private Dessert dessert;
​
    @Autowired
    @Qualifier("cold")
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }
    
}
​

可以看到,Spring想找id为cold的bean,可是发现有两个,也会报错

所以我们需要缩小范围,但是Java中不允许同样的注解标签,这时候我们可以使用自定义的限定符注解

即创建一个@Cold标签

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

//以上两个类中又@Qualifier标签,具有了其特性//现在我们的JavaConfig中就不会拥有相同的标签了

@Component
@Cold
@Fruit
public class Popsicle implements Dessert{
​
}
​
@Component
@Cold
@Creamy
public class Cake implements Dessert{
    
}
​
@Configuration
public class JavaConfig {
    
    private Dessert dessert;
​
    @Autowired
    @Cold
    @Creamy
    public void setDessert(Dessert dessert) {
        this.dessert = dessert;
    }
    
}

 

3.4 bean的作用域

不同的情况下,我们应该需要有不同的bean作用域,有时我们只需要单一的bean,有时却需要多个相同bean

Spring定义了多种作用域

  • 单例(Singleton):整个应用中,只创建一个bean

  • 原型(Protptype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例

  • 会话(Session):在Web应用中,为每个会话创建一个bean实例

  • 请求(Request):在Web应用中,为每个请求创建一个bean实例

 

在Bean中可以加标签@Scope

@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
//@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)两者都可以,不能同时使用
​
public class Cookies implements Dessert{
​
}
​

在xml中,可以以这种形式存在

<bean id="ice" class="com.myapp.IceCream" scope="prototype"></bean>

 

3.4.1 使用会话和请求作用域

在Web工程的商城购物车中,使用Session作用域的bean是非常好的事情,因为一人一个购物车

若是单例的bean,那每个人都得用同一个bean,将会不堪设想.

@Component
//WebApplicationContext此元素位于org.springframework.web.context.WebApplicationContext
//也可以自己观察源码
@Scope(value=WebApplicationContext.SCOPE_SESSION,
        proxyMode=ScopedProxyMode.INTERFACES)
public Interface ShoppingCart {
​
}
​
​
//将ShoppingCart注入Services中
@Component
public class StoreServices {
    private ShoppingCart shoppingcart;
​
    
    @Autowired
    public void setShoppingcart(ShoppingCart shoppingcart) {
        this.shoppingcart = shoppingcart;
    }
    
}

只有当ShoppingCart bean是会话作用域得,只有创建了会话后,才会出现ShoppingCart实例.

 

在当中有一个问题:

ShoppingCart 是多例得,而StoreServices是单例得,对于StoreServices处理购物车功能时,我们不会希望固定的一个bean,而时所用ShoppingCart实例恰好是当前会话所对应的那一个,所以Spring会将ShoppingCart 注入一个到ShoppingCart的代理对象中,其会进行兰姐系并将调用委托个会话作用域内真正的ShoppingCart bean.

 

 

proxyMode属性被设置成了ScopedProxyMode.INTERFACES,表明代理类需要实现此接口,并将调用委托给实现bean

proxyMode属性被设置成了ScopedProxyMode.TARGET_CLASS,表明代理类要以生成目标类扩展的方式创建代理,并将调用委托给实现bean

如果ShoppingCart是类的话就需要使用CGLib来生成代理

如果ShoppingCart是接口的话就需要使用jdk动态代理来生成代理

3.4.2 在XML中声明作用域代理

<!-- 默认使用了CGlib方式创建代理类 -->
    <bean id="cart" class="com.myapp.ShoppingCart" scope="session">
        <aop:scoped-proxy/>
    </bean>
    
    <!-- 使用jdk动态代理, ShoppingCart应该是个接口才可以使用-->
    <bean id="cart" class="com.myapp.ShoppingCart" scope="session">
        <aop:scoped-proxy proxy-target-class="false"/>
    </bean>

3.5运行时值注入

在许多时候,我们会希望自己的xml文件中避免硬编码,想要实现值在运行时才确定有两种方法

3.5.1 注入外部的值

@Configuration
//加载类路径下的/config/db.properties资源
@PropertySource("classpath:/config/db.properties")
public class JavaConfig {
    
    @Autowired
    Environment ev;
    
    
    public void PropertySource(Environment ev) {
        //取出资源中属性名为jdbc.url的值
        System.out.println(ev.getProperty("jdbc.url"));
        System.out.println(ev.getProperty("jdbc.password"));
    }
}   

至于Environment类如何获取值得方法,可以查询Environment和PropertyResolver接口中得源码

 

解析属性占位符

属性得占位符可以是 ,${}, #{}

但是在JavaConfig我们需要配置一个类

static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

或者在xml中配置

<context:property-placeholder/>

当二者选一后,我们才能使用占位符

 

3.5.2 使用SPring表达式语言进行装配

  • 使用bean得ID来引用bean

  • 调用方法和访问对象得属性

  • 对值进行算数,关系,和逻辑运算

  • 正则表达式匹配

  • 集合操作

 

样例可自行查看官网

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值