Spring实战第三章

1、环境与profilejava

  开发阶段中,某些环境相关作法可能并不适合迁移到生产环境中,甚至即使迁移过去也没法正常工做。数据库配置加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。node

  Spring并非在构建的时候作出这样的决策,而是等到运行时再来肯定。在这个过程当中须要根据环境决定该建立哪一个bean和不建立哪一个bean。这样的结果就是同一个部署单元(可能会是WAR文件)可以适用于全部的环境,没有必要进行从新构建。正则表达式

  Spring引入了bean profile的功能。要使用profile,你首先要将全部不一样的bean定义整理到一个或多个profile之中,在将应用部署到每一个环境时,要确保对应的profile处于激活(active)的状态。算法

在Java配置中,可使用@Profile注解指定某个bean属于哪个profile。spring

 1.1Javaconfig中配置sql

 1 package com.wang.three;
 2 
 3 import javax.sql.DataSource;
 4 
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.context.annotation.Profile;
 8 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
 9 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
10 
11 @Configuration
12 @Profile("dev")
13 public class DevelopmentProfileConfig {
14     @Bean(destroyMethod="shutdown")
15     public DataSource dataSource(){
16         return new EmbeddedDatabaseBuilder()
17         .setType(EmbeddedDatabaseType.H2)
18         .addScript("classpath:schema.sql")
19         .addScript("classpath:test-data.sql")
20         .build();
21     }
22 
23 }

 

注释:@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会建立。若是dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。数据库

Spring 3.2开始,你也能够在方法级别上使用@Profile注解,与@Bean注解一同使用。能够在一个JavaConfig中配置多个profile数组

 1 package com.wang.three;
 2 
 3 import javax.sql.DataSource;
 4 
 5 import org.springframework.context.annotation.Bean;
 6 import org.springframework.context.annotation.Configuration;
 7 import org.springframework.context.annotation.Profile;
 8 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
 9 import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
10 import org.springframework.jndi.JndiObjectFactoryBean;
11 
12 @Configuration
13 public class DatasourceConfig {
14     
15     @Bean(destroyMethod="shutdown")
16     @Profile("dev")
17     public DataSource DevelopmentDataSource(){
18         return new EmbeddedDatabaseBuilder()
19         .setType(EmbeddedDatabaseType.H2)
20         .addScript("classpath:schema.sql")
21         .addScript("classpath:test-data.sql")
22         .build();
23     }
24     
25     @Bean
26     @Profile("prod")
27     public DataSource ProductionDataSource(){
28         JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
29         jndiObjectFactoryBean.setJndiName("jdbc/myDs");
30         jndiObjectFactoryBean.setResourceRef(true);
31         jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
32         return (DataSource)jndiObjectFactoryBean.getObject();
33     }
34 }

 

尽管每一个DataSource bean都被声明在一个profile中,而且只有当规定的profile激活时,相应的bean才会被建立,可是可能会有其余的bean并无声明在一个给定的profile范围内。没有指定profile的bean始终都会被建立,与激活哪一个profile没有关系。安全

1.2xml中配置session

全部的配置文件都会放到部署单元之中(如WAR文件),可是只有profile属性与当前激活profile相匹配的配置文件才会被用到。

profile="dev"> 也能够新键其余的配置文件

<?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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
    profile="dev">
    
    <jdbc:embedded-database id="dataSource">
        <jdbc:script location="classpath:schema.sql"/>
        <jdbc:script location="classpath:test-data.sql"/>
    </jdbc:embedded-database>
</beans>

 

还能够在根<beans>元素中嵌套定义<beans>元素,而不是为每一个环境都建立一个profile XML文件。这可以将全部的profile bean定义放到同一个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:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource" type="H2">
      <jdbc:script location="classpath:schema.sql" />
      <jdbc:script location="classpath:test-data.sql" />
    </jdbc:embedded-database>
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      lazy-init="true"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>

有两个bean,类型都是javax.sql.DataSource,而且ID都是dataSource。可是在运行时,只会建立一个bean,这取决于处于激活状态的是哪一个profile

1.3激活profile

Spring在肯定哪一个profile处于激活状态时,须要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。若是设置了spring.profiles.active属性的话,那么它的值就会用来肯定

哪一个profile是激活的。但若是没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。

若是spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,所以只会建立那些没有定义在profile中的bean。

使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile,我会在Servlet上下文中进行设置(为了兼顾到ContextLoaderListener)。

当应用程序部署到QA、生产或其余环境之中时,负责部署的人根据状况使用系统属性、环境变量或JNDI设置spring.profiles.active便可。

在spring.profiles.active和spring.profiles.default中,profile使用的都是复数形式。这意味着你能够同时激活多个profile,这能够经过列出多个profile名称,并以逗号分隔来实现。

Spring提供了@ActiveProfiles注解,咱们可使用它来指定运行测试时要激活哪一个profile。

(@ActiveProfiles("dev"))

2、条件化的bean

 Spring 4引入了一个新的@Conditional注解,它能够用到带有@Bean注解的方法上。若是给定的条件计算结果为true,就会建立这个bean,不然的话,这个bean会被忽略。

举例:假设有一个名为MagicBean的类,咱们但愿只有设置了magic环境属性的时候,Spring才会实例化这个类。若是环境中没有这个属性,那么MagicBean将会被忽略。

//MagicBean
package com.wang.three;
public class MagicBean { }

 

使用@Conditional注解条件化地配置了MagicBean。

package com.wang.three;

import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;

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

}

 

设置给@Conditional的类(例如MagicExistsCondition)能够是任意实现了Condition接口的类型(这个接口是Spring中的接口)。

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 context, AnnotatedTypeMetadata metadata) {
        // TODO Auto-generated method stub
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");
    }

}

 

这个接口实现起来很简单直接,只需提供matches()方法的实现便可。若是matches()方法返回true,那么就会建立带有@Conditional注解的bean。若是matches()方法返回false,将不会建立这些bean。

它经过给定的ConditionContext对象进而获得Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。只要属性存在便可知足要求。若是知足这个条件的话,matches()方法就会返回true。所带来的结果就是条件可以获得知足,全部@Conditional注解上引用MagicExistsCondition的bean都会被建立。matches()方法会获得ConditionContext和AnnotatedTypeMetadata对象用来作出决策。

 

ConditionContext是一个接口:

  

 经过ConditionContext,咱们能够作到以下几点:

   一、借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;

   二、借助getBeanFactory()返回的ConfigurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

   三、借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;

   四、读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;

   五、借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata也是一个接口,可以让咱们检查带有@Bean注解的方法上还有什么其余的注解。

 

 

 

 

 

 

借助isAnnotated()方法,咱们可以判断带有@Bean注解的方法是否是还有其余特定的注解。借助其余的那些方法,咱们可以检查@Bean注解的方法上其余注解的属性。

@Profile注解是基于@Conditional和Condition实现。

 

3、处理自动装配bean产生的歧义

 自动装配歧义的产生:

Dessert是一个接口,而且有三个类实现了这个接口,分别为Cake、Cookies和IceCream:

@Component
public class Cake implements Dessert {

}

@Component
public class Cookies implements Dessert {

}

@Component
public class IceCream implements Dessert {

}

 

在自动化装配时,

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

由于这三个实现均使用了@Component注解,在组件扫描的时候,可以发现它们并将其建立为Spring应用上下文里面的bean。而后,当Spring试图自动装配setDessert()中的Dessert参数时,它并无

惟1、无歧义的可选值。以后会发生异常(NoUniqueBeanDefinitionException)。

 解决方案:

 3.1首选的bean

  在Spring中,能够经过@Primary来表达最喜欢的方案。

    @Primary可以与@Component组合用在组件扫描的bean上

import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;

@Component
@Primary
public class IceCream implements Dessert {

}

 

        @Primary与@Bean组合用在Java配置的bean声明中

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

 

      用XML配置bean,<bean>元素有一个primary属性用来指定首选的bean

  

注意:若是你标示了两个或更多的首选bean,那么它就没法正常工做了

  3.2限定自动装配的bean

  @Qualifier注解是使用限定符的主要方式。它能够与@Autowired和@Inject协同使用。

  3.2.1 使用默认的限定符:若是没有指定其余的限定符的话,全部的bean都会给定一个默认的限定符,这个限定符与bean的ID相同。

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

 

     3.2.2建立自定义的限定符

  自定义了限制符的名称

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

 

进行引用

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

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

 3.2.3自定义的限定符注解

  若是多个bean都具有相同特性的话,这种作法也会出现重匹配的问题。(能够用多组描述区分)可是因为Java中不允许相同注解的出现,故须要自定义注解

  建立自定义的限定符注解,借助这样的注解来表达bean所但愿限定的特性。这里所须要作的就是建立一个注解,它自己要使用@Qualifier注解来标注。这样咱们将再也不使用@Qualifier("cold"),而是使用自定义的@Cold注解。其用法和@Qualifier同样

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

}

 

4、bean的做用域

 Spring定义了多种做用域,能够基于这些做用域建立bean,包括:

   单例(Singleton):在整个应用中,只建立bean的一个实例。

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

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

  请求(Rquest):在Web应用中,为每一个请求建立一个bean实例。

 在默认状况下,Spring应用上下文中全部bean都是做为以单例(singleton)的形式建立的。也就是说,无论给定的一个bean被注入到其余bean多少次,每次所注入的都是同一个实例。

 若是选择其余的做用域,要使用@Scope注解,它能够与@Component或@Bean一块儿使用

 4.1原型

使用组件扫描来发现和声明bean,那么你能够在bean的类上使用@Scope注解,将其声明为原型bean:

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//或为(@Scope("prototype"))
public class Notepad {
  // the details of this class are inconsequential to this example
}

 

 想在Java配置中将Notepad声明为原型bean,那么能够组合使用@Scope和@Bean来指定所需的做用域:

@Bean
@Scope("prototype")
public NodePad nodePad(){
    return new NodePad();
}

使用XML来配置bean的话,可使用<bean>元素的scope属性来设置做用域:

 4.2会话与请求域

  就购物车bean来讲,会话做用域是最为合适的,由于它与给定的用户关联性最大。

  将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每一个会话建立一个ShoppingCart。这会建立多个ShoppingCart bean的实例,可是对于给定的会话只会建立一个实例,在当前会话相关的操做中,这个bean实际上至关于单例的。

    

   对于:proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求做用域的bean注入到单例bean中所遇到的问题。

    例如当把其注入到一个单例中

     Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理,这个代理会暴露与ShoppingCart相同的方法,因此StoreService会认为它就是一个购物车。可是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话做用域内真正的ShoppingCart bean。

  proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这代表这个代理要实现ShoppingCart接口,并将调用委托给实现bean。

  若是ShoppingCart是接口而不是类的话,这是能够的(也是最为理想的代理模式)。但若是ShoppingCart是一个具体的类的话,Spring就没有办法建立基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。因此,若是bean类型是具体类的话,咱们必需要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来代表要以生成目标类扩展的方式建立代理。

  请求做用域与之相似。

     4.3在xml中声明做用域及代理

  使用XML来声明会话或请求做用域的bean,那么就使用<bean>元素的scope属性;要设置代理模式,须要使用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
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">
     <bean id="cart" 
             class="com.wang.three.ShoppingCart"
             scope="session">
       <aop:scoped-proxy/>        
     </bean>
     
     </beans>

 

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

<?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
     http://www.springframework.org/schema/aop
     http://www.springframework.org/schema/aop/spring-aop.xsd">
     <bean id="cart" 
             class="com.wang.three.ShoppingCart"
             scope="session">
       <aop:scoped-proxy proxy-target-class="false"/>        
     </bean>
     
     </beans>

 

5、运行时植入

  当望避免硬编码值,而是想让这些值在运行时再肯定。Spring提供了两种在运行时求值的方式:

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

  5.1注入外部值

  在Spring中,处理外部值的最简单方式就是声明属性源并经过Spring的Environment来检索属性。

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

 

    5.1.1学习Spring的Environment

   Spring中使用getProperty(),有四个重载

    a、String getProperty(String key)

    b、String getProperty(String key,String defaultValue)

    c、T getProperty(String key,Class<T> type)

    d、T getProperty(String key,Class<T> type, T defaultValue)

  使用a、b都会返回的是一个String类型的值,可是b会在指定的属性key不存在的时候返回一个默认的值。

  而对于它们不会将全部的值都视为String类型,而是根据type来肯定。

int connectionCount = env.getProperty("db.connection.count",Interger.class,30);

 

   若是disc.title或disc.artist属性没有定义的话且没有设置默认值,将会抛出IllegalStateException异常。

  a、能够调用Environment的containsProperty()方法:检查一下某个属性是否存在的话。

boolean titleExists = env.containsProperty("disc.title");

 

 b、将属性解析为类

  自定义的CompactDisc的类

    Class<CompactDisc> cdClass = env.getPropertyAsClass("disc.class", CompactDisc.class);

 

 c、检验哪些profile处于激活状态

  String[] getActiveProfiles():返回激活profile名称的数组;

  String[] getDefaultProfiles():返回默认profile名称的数组;

  boolean acceptsProfiles(String... profiles):如environment支持给定profile的话,就返回true。

    (实际的例子没有写,详情看书91页和78页)

  5.1.2占位符

  Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称

  做为样例,咱们能够在XML中按照以下的方式解析BlankDisc构造器参数:

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

 

依赖于组件扫描和自动装配来建立和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种状况下,咱们可使用@Value注解。

    public BlankDisc(@Value("${disc.title}") String title,@Value("${disc.artist}")String artist){
        this.title=title;
        this.artist=artist;
    }

 

解析配置的占位符:

  为了使用占位符,咱们必需要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。

  推荐使用PropertySourcesPlaceholderConfigurer,由于它可以基于Spring Environment及其属性源来解析占位符。

  

使用@bean方法在Java中配置了PropertySourcesPlaceholderConfigurer

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
        return new PropertySourcesPlaceholderConfigurer();
    }
    

 

在xml中也能够配置,Spring context命名空间中的<context:propertyplaceholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean:

  

 

总之:解析外部属性可以将值的处理推迟到运行时,可是它的关注点在于根据名称解析来自于Spring Environment和属性源的属性

5.2强大的SpEL(Spring 表达式语言)

  能够实现外部注入等等,可以应用在DI以外的其余地方

  简介:以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程当中所使用的表达式会在运行时计算获得值

  SpEL拥有不少特性,包括:

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

     a、SpEL表达式要放到“#{ ... }”之中。

   b、T()表达式

      若是要在SpEL中访问类做用域的方法和常量的话,要依赖T()这个关键的运算符。

     #{T(Syste).currentTimeMillis()}//会将java.lang.System视为Java中对应的类型

   c、systemProperties对象引用系统属性

    如:#{systemProperties('disc.title')}

     相似属性占位符的例子,组件扫面建立bean

    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表达式设置的:

    

           d、字面值

      整数、浮点数、String值和Boolean

      #{1}、#{1.2}、#{9.87E3}、#{true}

    f、引用bean、属性和方法

      经过ID引用其余的bean 例:#{SgtPeppers}

      或其属性#{SgtPeppers.artist}

      或调用bean中方法,对于被调用方法的返回值来讲,咱们一样能够调用它的方法 #{artSelect.selectArtist().toUpperCase()} 。

        为了安全起见:使用了“?.”运算符。这个运算符可以在访问它右边的内容以前,确保它所对应的元素不是null。

          #{artSelect.selectArtist()?.toUpperCase()}

    e、SqEL运算符

      

运算符类型 运算符
算数运算符 +、-、 * 、/、%、^
比较运算符 < 、 > 、 == 、 <= 、 >= 、 lt 、 gt 、 eq 、 le 、 ge
逻辑运算符 and 、 or 、 not 、│
条件运算符 ?: (ternary) 、 ?: (Elvis)
正则表达式 matches

   三元运算符的一个常见场景就是检查null值,并用一个默认值来替代null。例如,以下的表达式会判断disc.title的值是否是null,若是是null的话,那么表达式的计算结果就会是“Rattleand Hum”:

        

   f、正则表达式

      

   g、计算集合

       要从jukebox中随机选择一首歌:

        

      取字符串的字符

        

      查询运算符(.?[]),它会用来对集合进行过滤,获得集合的一个子集 

        //但愿获得jukebox中artist属性为Aerosmith的全部歌曲

                “.^[]”和“.$[]”,它们分别用来在集合中查询第一个匹配项和最后一个匹配项。

              投影运算符(.![]),它会从集合的每一个成员中选择特定的属性放到另一个集合中。

                         //将title属性投影到一个新的String类型的集合中

相关文章
相关标签/搜索