《Spring实战》学习笔记 - 第3章 高级装配

环境与profile

配置profile bean

  • 在开发过程中,每个阶段的环境的配置有所不同,需要为每种环境重新构建应用。而通过Spring配置profile bean提供了解决方案
  • 通过注解@Profile来指定某一个bean属于哪一个profile,例如@Profile("dev")属于开发环境的profile,此bean只会在devprofile激活时才会创建
  • @Profile可以和@Bean一同使用
  • 也可以通过xml配置文件来配置profile,例如<beans profile="dev"></beans>

激活profile

  • 需要依赖两个独立的属性: spring.profiles.activespring.profiles.default
  • 设置spring.profiles.active,会用来确定那个profile是激活的
  • 没有设置spring.profiles.active,则会寻找spring.profiles.default
  • 两者均没设置,则没有激活的profile

激活方式

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

    <!-- 为上下文设置默认的profile -->
    <context-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </context-param>
    
    <servlet>
        <servlet-name>appServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 为DispatcherServlet设置默认的profile -->
        <init-param>
            <param-name>spring.profiles.default</param-name>
            <param-value>development</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

条件化的bean

通过注解@Conditional来实现

@Configuration
public class MagicConfig {
    @Bean
    @Conditional(MagicExistsCondition.class) // 条件化创建bean
    public static MagicBean magicBean() {
        return new MagicBean();
    }
}
  • 设置给@Conditional的类可以是任意实现了Condition接口的类型

    public interfact Condition {
        boolean matches(ConditonContext ctxt, AnnotatedTypeMetadata metadata)
    }
    • 只需要实现matches方法即可,返回true则创建bean,否则不会创建
  • Condition接口实现示例

    public class MagicExistsCondition implements Condition{
        @Override
        public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
            Environment env = context.getEnvironment();
            return env.containsProperty("magic"); //检查magic属性
        }
    }
    • 通过给定的ConditionContext对象获得Environment对象,并且检查此环境中是否存在名为magic的环境属性
    • @Profile本身使用了@Conditional注解,并且引用ProfileConditon作为Condition的实现

处理自动装配的歧义性

当多个类是实现了统一接口,而在装配此接口时,Spring没有唯一的可选值,会抛出异常

标示首选的bean

  • 使用@Primary注解,与@Component或者@Bean等结合使用

    // 使用Component
    @Component
    @Primary
    public class IceCream implements Dessert {...}
    
    // 使用Bean
    @Bean
    @Primary
    public class IceCream implements Dessert {...}
  • 或者在xml中<bean>标签的primary="true"属性来配置
  • 但多个类被标注为@Paimary时,装配仍然会出现异常

限定自动装配的bean

  • 在装配时使用@Qualifier限定符

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

创建自定义限定符

  • @Component@Bean等注解后添加自定义的@Qualifier
  • 在装配时,即可使用自定义的限定符

    // 使用Component,使用Bean注解类似
    @Component
    @Qualifier("cold")
    public class IceCream implements Dessert {...}
    @Autowired
    @Qualifier("cold")
    public void setDessert(Dessert dessert) {
        this.dessert = dessert
    }
  • 当多个bean使用同一个@Qualifier时,依然会出现冲突
  • 可以通过再添加一个Qualifier解决

    // 使用Component,使用Bean注解类似
    @Component
    @Qualifier("cold")
    @Qualifier("creamy")
    public class IceCream implements Dessert {...}
    @Autowired
    @Qualifier("cold")
    @Qualifier("creamy")
    public void setDessert(Dessert dessert) {
        this.dessert = dessert
    }

bean的作用域

默认情况下bean都是单例的形式创建的

Spring定义了多种作用域

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

使用会话和请求作用域

  • 例如电商中的购物车: 如果为单例,则所有用户共享同一购物车;如果为多例,则每个商品都放在不同的购物车里。因此会话作用域更加适合

    • 定义会话作用域:
    @Component
    @Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
    public class ShoppingCart {...}
    • value的值为WebApplicationContext.SCOPE_SESSION即会话
    • proxyMode解决问题的场景

      @Component
      public class StoreService {
          @Autowired
          public void setShoppingCart(ShoppingCart shoppingCart) {...}
      }
      • StoreService是一个单例的bean,在Spring应用上下文加载的时候创建。当其创建后,用户并未发起会话,因此并不存在bean。而多个用户使用时,我们希望StoreService只在处理购物车功能时,使用当前的ShoppingCartbean实例
      • Spring并不会将实际的ShoppingCartbean注入到StoreService中,而是注入一个到ShoppingCartbean的代理。这个代理会暴露和ShoppingCart相同的方法,但是当StoreService调用时,代理会将其解析并且调用委托给会话作用域内真正的ShoppingCartbean
      • 因此proxyMode属性被设置为ScopedProxyMode.INTERFACES表明代理需要实现ShoppingCart接口,并将调用委托给实例bean,此方法为最理想的代理模式
      • 如果ShoppingCart是类的话,Spring将无法创建基于接口的代理,则必须使用CGLib来生成基于类的代理,而proxyMode属性值为ScopedProxyMode.TARGET_CLASS

使用请求作用域是也同样应该通过作用域代理的方式进行注入

在XML中声明作用域代理

<bean id="example" class="com.eric.exmaple.Example" scpoe="session">
    <aop:scoped-proxy proxy-target-class="false">
</bean>
  • <bean>中的scope属性可以选择作用域,通过<aop:scoped-proxy proxy-target-class="false">来配置proxyMode属性,默认基于类创建代理,属性值设置为false则基于接口创建代理

运行时值注入

Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder)
  • Spring表达式语言(SpEL)

注入外部的值

  • 处理外部值最简单的方式就是声明属性源并通过Spring的Environment检索属性
  • 示例:

    @Configuration
    @PropertySource("classpath:/com/Eric/example.properties") // 声明属性源
    public class ExpressiveConfig {
        @Autowired
        Environment env;
    
        @Bean
        public BlankDisc disc() {
            return env.getProperty("title");
        }
    }
    • 通过@PropertySource引用外部的属性源,并且该属性文件会加载到Spring的Environment中,可以在Environment中检索到属性
  • 深入学习Spring的Environment

    • Environment有四种getProperty()重载方法:

      • String getProperty(String key): 通过key查找值,返回String
      • String getProperty(String key, String defaultValue): 通过key查找值,返回String,查找不到时使用默认值defaultValue
      • T getProperty(String key, Class<T>type): 通过key查找值,并且将结果转换为T类型,查找不到时返回null
      • T getProperty(String key, Class<T>type, T defaultValue): 通过key查找值,并且将结果转换为T类型,查找不到时使用默认值defaultValue
    • containProperty(): 检查某个属性是否存在

    • getPropertyAsClass(): 将属性解析为类
    • String[] getActiveProfiles(): 返回激活profile名称的数组
    • String[] getDefaultProgiles(): 返回默认profile名称的数组
    • boolean acceptsProfiles(String...properties): 如果environment支持给定的profile,则返回true
  • 解析属性占位符

    • Spring支持将属性定义到外部的属性文件中,并使用占位符值将其插入到Spring bean中
    • 占位符的形式为使用${...}包装的属性名称
    • 在XML中注入参数示例:

      <bean id="example" 
            class="com.eric.Example"
            c:_title="${disc.title}"
            c:_artist="${disc.artist}"/>
    • 在自动装配中通过@Value注解来实现

      public class BlankDisc {
          public BlankDisc(
              @Value("${disc.title}") String title,
              @Value("${disc.artist}") String artist) {
          this.title = title;
          this.artist = artist;
          }
      }
    • 在使用占位符时,需要配置一个PropertyPlaceholderConfigurerbean或PropertySourcePlaceholderConfigurerbean,推荐使用后者

      @Bean
      public static PropertySourcePlaceholderConfigurer placeholderConfigurer() {
          return new PropertySourcePlaceholderConfigurer();
      }
      <!-- 或者通过配置XML文件 -->
      <context:property-placdholder/>

使用Spring表达式语言进行装配
Spring Expression Language - SpEL,能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中

  • SpEL特性:

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

    • SpEL表达式需要放到#{...}中,与属性占位符类似
    • #{T(System).currentTimeMillis()}
      • T()会将括号中的内容视为对应的Java对象
    • #{numb.artist}
      • 通过bean的id(numb),来引用bean的属性(artist)
  • 在bean装配时运用表达式:

public class BlankDisc {
    public BlankDisc(
            @Value("#{systemProperties['disc.title']}") String title, 
            @Value("#{systemProperties['disc.artist']}") String artist) {
        this.title = title;
        this.artist = artist;
    }
}
  • 引用bean、属性和方法

    • SpEL能够通过ID来引用对应的bean
    • #{bean.attribute}引用id为beanattribute属性
    • #{bean.method()}引用id为beanmethod方法,获取方法返回值
    • #{bean.method()?.toUpperCase()}引用id为beanmethod方法,获取方法返回值(假设此处的返回值为String),调用String的toUpperCase()方法。?用于判断方法返回值是否为null(为null,则不调用后面的方法)
  • 在表达式中使用类型

    • 通过T()获取java中的类,例如#{T(java.lang.Math)}获取Math类
    • 此方法的价值在于能够访问目标类型的静态方法和常量,例如#{T(java.lang.Math).PI}获取PI的值
  • SpEL运算符

    • 示例:
      • 计算圆的周长#{2 * T(java.lang.Math).PI * circle.radius}
      • 计算圆的面积#{T(java.lang.Math).PI * circle.radius ^ 2}
      • 合并String#{disc.title + 'by' + disc.artist}
      • 判断值#{counter.total == 100}#{counter.total eq 100}
      • 三元运算符#{scoreboard.socre > 100 ? "Winner" : "Loser"}
      • Elvis运算符#{disc.title ?: 'Hello'}判断值是否为null,为空则结果为'Hello'
  • 计算正则表达式

    • 通过matches来匹配
    • 示例: #{admin.email matches '[a-zA-Z0-9._%+-]+@[a-z-A-Z0-9.-]+\\.com'}
  • 计算集合

    • #{bean.list[i].attribute}获取beanlist列表的i个元素的attribute属性
    • #{'string'[i]}获取字符串'string'的第i个字符
    • 查询运算符
      • .?[]: 查询集合#{bean.list.?[attribute eq 'example']}获取beanlist列表中所有attribute属性为'example'的元素
      • .^[]: 查询第一个匹配项
      • .$[]: 查询最后一个匹配项
      • .![] 投影运算符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值