Spring源码剖析-BeanFactoryPostProcessors 应用之 PropertyPlaceholderConfigurer
视频在线率统计——基于驱动总线设备的领域驱动设计方法落地
BeanFactoryPostProcessors
引见
BeanFactoryPostProcessors完全定义:
/**
* Allows for custom modification of an application context's bean definitions,
* adapting the bean property values of the context's underlying bean factory.
* @see BeanPostProcessor
* @see PropertyResourceConfigurer
*/
public interface BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean factory after its standard
* initialization. All bean definitions will have been loaded, but no beans
* will have been instantiated yet. This allows for overriding or adding
* properties even to eager-initializing beans.
* @param beanFactory the bean factory used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
我们晓得spring最大长处就是其可扩大性,BeanFactoryPostProcessor接口就是spring中提供应我们用于扩大的一个处所。我们看该接口上的javadoc实在异常的细致,这也是我们看spring源码的一个技能,就是看一个类是干吗的肯定要先通读其解释。
连系接口上的解释大抵形貌下BeanFactoryPostProcessor:
许可用户经由过程修正applicationContext 中的bean定义(就是xml中定义的bean的信息即:BeanDefinition是和xml有一对一的设置,比方是不是是单利,以及propert 属性的赋值等)
来调解applicationContext中bean工场中bean属性值。
也就是说实行到BeanFactoryPostProcessor时悉数的BeanDefinition定义已加载好了然则bean实例还没有被建立,我们能够修补或许掩盖bean属性值。
我们能够看一下ApplicationContext中BeanFactoryPostProcessor的挪用位置来印证是不是云云
下面是ApplicationContext中心代码:
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
//猎取beanFactory实例
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
try {
postProcessBeanFactory(beanFactory);
//这里恰是我们的BeanFactoryPostProcessor实行的位置
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//建立非懒加载的一切单例 这里是真正建立bean实例的处所
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
经由
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
这两步我们已拿到了beanFactory实例,也就是每个bean对应的BeanDefinition已加载好了。下面才是实行invokeBeanFactoryPostProcessors(beanFactory),也就印证了我们上面的结论。
下面我们经由过程一个BeanFactoryPostProcessor的典范运用PropertyPlaceholderConfigurer来细致解说BeanFactoryPostProcessor实行道理
PropertyPlaceholderConfigurer
简朴运用的例子
PropertyPlaceholderConfigurer置信人人都运用过,我们在设置bean的属性能够运用占位符来赋值,然后经由过程调解properties文件中对应的属性值来修正。看一个运用PropertyPlaceholderConfigurer简朴的例子:
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
<bean class="com.yalunwang.Student" id="student">
<property name="name" value="${student.name}"></property>
<property name="age" value="${student.age}"></property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:bean.properties"></property>
</bean>
bean.properties设置文件:
student.name=anan
student.age=2
单元测试:
@Test
public void test_ioc_app(){
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-ioc.xml");
Student student =(Student) ac.getBean("student");
System.out.println("name: "+student.getName());
System.out.println("age: "+student.getAge());
}
输出效果一切一般:
name: anan
age: 2
源码剖析
先看一下PropertyPlaceholderConfigurer的类继承图:
能够看到PropertyPlaceholderConfigurer完成了BeanFactoryPostProcessor和 PriorityOrdered。
我们接着对上面的 invokeBeanFactoryPostProcessors(beanFactory)继承举行剖析:
/**
* Instantiate and invoke all registered BeanFactoryPostProcessor beans,
* respecting explicit order if given.
* 实例化并挪用一切已注册的BeanFactoryPostProcessor Bean,
* 假如继承了Order接口按次序实行
* <p>Must be called before singleton instantiation.
*/
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
// Invoke BeanDefinitionRegistryPostProcessors first, if any.
Set<String> processedBeans = new HashSet<String>();
//假如是beanFactory完成了BeanDefinitionRegistry
if (beanFactory instanceof BeanDefinitionRegistry) {
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
List<BeanFactoryPostProcessor> regularPostProcessors = new LinkedList<BeanFactoryPostProcessor>();
List<BeanDefinitionRegistryPostProcessor> registryPostProcessors =
new LinkedList<BeanDefinitionRegistryPostProcessor>();
//遍历硬编码设置的beanFactory后置处置惩罚器
for (BeanFactoryPostProcessor postProcessor : getBeanFactoryPostProcessors()) {
//假如是BeanDefinitionRegistryPostProcessor范例先实行postProcessBeanDefinitionRegistry要领再将其增加到registryPostProcessors鸠合中举行后续postProcessBeanFactory要领的实行
if (postProcessor instanceof BeanDefinitionRegistryPostProcessor) {
BeanDefinitionRegistryPostProcessor registryPostProcessor =
(BeanDefinitionRegistryPostProcessor) postProcessor;
registryPostProcessor.postProcessBeanDefinitionRegistry(registry);
registryPostProcessors.add(registryPostProcessor);
}
else {
//同理将一般的beanFactory后置处置惩罚器增加到regularPostProcessors鸠合中举行后续postProcessBeanFactory要领的实行
regularPostProcessors.add(postProcessor);
}
}
//找出设置的BeanDefinitionRegistryPostProcessor后置处置惩罚器
Map<String, BeanDefinitionRegistryPostProcessor> beanMap =
beanFactory.getBeansOfType(BeanDefinitionRegistryPostProcessor.class, true, false);
List<BeanDefinitionRegistryPostProcessor> registryPostProcessorBeans =
new ArrayList<BeanDefinitionRegistryPostProcessor>(beanMap.values());
OrderComparator.sort(registryPostProcessorBeans);
//实行BeanDefinitionRegistryPostProcessor后置处置惩罚器的postProcessBeanDefinitionRegistry要领
for (BeanDefinitionRegistryPostProcessor postProcessor : registryPostProcessorBeans) {
postProcessor.postProcessBeanDefinitionRegistry(registry);
}
//实行上面增加的beanFactory后置处置惩罚器的鸠合里的要领
invokeBeanFactoryPostProcessors(registryPostProcessors, beanFactory);
invokeBeanFactoryPostProcessors(registryPostProcessorBeans, beanFactory);
invokeBeanFactoryPostProcessors(regularPostProcessors, beanFactory);
//处置惩罚后的增加到鸠合里 防备背面重复实行
processedBeans.addAll(beanMap.keySet());
}
else {
// Invoke factory processors registered with the context instance.
invokeBeanFactoryPostProcessors(getBeanFactoryPostProcessors(), beanFactory);
}
//猎取设置的BeanFactoryPostProcessor
//以下按完成了 PriorityOrdered Ordered 没有继承 三种举行优先级排序实行
String[] postProcessorNames =
beanFactory.getBeanNamesForType(BeanFactoryPostProcessor.class, true, false);
List<BeanFactoryPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
List<String> orderedPostProcessorNames = new ArrayList<String>();
List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
for (String ppName : postProcessorNames) {
//这个就是上面纪录的假如已处置惩罚了设置的BeanFactoryPostProcessors就跳过
if (processedBeans.contains(ppName)) {
// skip - already processed in first phase above
}
else if (isTypeMatch(ppName, PriorityOrdered.class)) {
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanFactoryPostProcessor.class));
}
else if (isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
}
else {
nonOrderedPostProcessorNames.add(ppName);
}
}
// First, invoke the BeanFactoryPostProcessors that implement PriorityOrdered.
OrderComparator.sort(priorityOrderedPostProcessors);
//上面我们说了PropertyPlaceholderConfigurer 完成了BeanFactoryPostProcessor和 PriorityOrdered,所以会在这一步实行挪用
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
// Next, invoke the BeanFactoryPostProcessors that implement Ordered.
List<BeanFactoryPostProcessor> orderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : orderedPostProcessorNames) {
orderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
OrderComparator.sort(orderedPostProcessors);
invokeBeanFactoryPostProcessors(orderedPostProcessors, beanFactory);
// Finally, invoke all other BeanFactoryPostProcessors.
List<BeanFactoryPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanFactoryPostProcessor>();
for (String postProcessorName : nonOrderedPostProcessorNames) {
nonOrderedPostProcessors.add(getBean(postProcessorName, BeanFactoryPostProcessor.class));
}
invokeBeanFactoryPostProcessors(nonOrderedPostProcessors, beanFactory);
}
上面主要的实行逻辑我都增加了中文解释轻易人人邃晓。
总结一下改要领主要做的事变:
- 先推断假如beanFactory是BeanDefinitionRegistry范例的话就增加对BeanDefinitionRegistryPostProcessor范例的挪用,BeanDefinitionRegistryPostProcessor接口是BeanFactoryPostProcessor的子接口,BeanDefinitionRegistryPostProcessor的作用是轻易我们能够手动注册bean交给spring来治理,能够经由过程扩大其唯一的要领(void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;) 来注册bean到spring里,这个不是本次剖析的重点背面再举例解说。
- 先对硬编码设置的BeanFactoryPostProcessor举行处置惩罚 假如是BeanDefinitionRegistryPostProcessor范例会举行postProcessBeanDefinitionRegistry挪用和postProcessBeanFactory挪用,假如不是则只举行postProcessBeanFactory挪用。
- 再对设置的BeanDefinitionRegistryPostProcessor举行处置惩罚(postProcessBeanDefinitionRegistry挪用和postProcessBeanFactory挪用)
- 末了对设置BeanFactoryPostProcessor的举行处置惩罚会按 PriorityOrdered/Ordered/没有继承任何排序接口 三种举行优先级排序实行postProcessBeanFactory挪用。个中我们的PropertyPlaceholderConfigurer 完成了BeanFactoryPostProcessor和 PriorityOrdered,所以会在这一步实行挪用
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
- ** 我们继承举行剖析:**
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
会实行PropertyPlaceholderConfigurer父类PropertyResourceConfigurer中的要领
/**
* {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
* {@linkplain #processProperties process} properties against the given bean factory.
* @throws BeanInitializationException if any properties cannot be loaded
*/
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
mergeProperties()要领会先将 设置的properties加载到mergedProps里
背面挪用** processProperties(beanFactory, mergedProps)**;举行处置惩罚
- processProperties(beanFactory, mergedProps)剖析
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
this.doProcessProperties(beanFactoryToProcess, valueResolver);
}
这里只要两行代码,第一行是建立StringValueResolver实例(用于替代占位符的真正要领)
我们跨过千山万水终究要到立时要举行占位符替代了,继承剖析
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
//将建立的valueResolver设置到BeanDefinitionVisitor里 用于终究替代逻辑 (替代占位符为对应properties里设置的值)
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//拿出ioc容器里注册的beans
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
//排撤除当前beanName也就是 PropertyPlaceholderConfigurer Bean 且beanFactoryToProcess必需是当前容器
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
//猎取一个bean对应的bean定义
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//举行bean定义元数据的替代操纵
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage());
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
上面我已运用中文解释写的很清晰了接着举行剖析
- visitor.visitBeanDefinition(bd);
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
visitPropertyValues(beanDefinition.getPropertyValues());
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
这里能够对应 bean的parentName beanClassName property等举行替代操纵我们这里只关注属性的替代操纵
- visitPropertyValues(beanDefinition.getPropertyValues());
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
pvs.add(pv.getName(), newVal);
}
}
}
- Object newVal = resolveValue(pv.getValue());
@SuppressWarnings("rawtypes")
protected Object resolveValue(Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}
这里有许多范例,是由于spring支撑许多范例的设置比方property的值我们能够设置为ref=xxxbean那末value就是RuntimeBeanReference范例,
假如设置
<list>
<value>343</value>
<value>45</value>
</list>
那末value就是List范例等等。这里我们例子中设置的范例是TypedStringValue,那末实行
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
//这里拿到的值是原始的带有占位符的比方例子里就是${student.name}、${student.age}这类
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
//这一步就是去举行替代
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
- String visitedString = resolveStringValue(stringValue);
/**
* Resolve the given String value, for example parsing placeholders.
* @param strVal the original String value
* @return the resolved String value
*/
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
//挪用我们之前传进来的valueResolver也就是上面 (BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); 这里传进来的) 举行替代操纵
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
valueResolver实在就是PlaceholderResolvingStringValueResolver实例,它又托付PropertyPlaceholderHelper举行操纵
也就是
/**
* Replaces all placeholders of format {@code ${name}} with the value returned
* from the supplied {@link PlaceholderResolver}.
* @param value the value containing the placeholders to be replaced.
* @param placeholderResolver the {@code PlaceholderResolver} to use for replacement.
* @return the supplied value with placeholders replaced inline.
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "Argument 'value' must not be null.");
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder buf = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
//将ex. ${student.name} 转成 student.name
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 递归挪用直到没有占位符
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 猎取properties设置文件对应的值 就是猎取student.name 对应在 properties里的值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
//再次递归挪用 确保末了的值没有占位符
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
//将本来的StringBuff的原始值替代为拿到的值
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
//这里由于已替代成真正的值 拿不到占位符(${)所以值就是-1 会跳出轮回返回
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in string value "" + strVal + """);
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
return buf.toString();
}
该要领是真正替代的操纵地点,改要领已增加中文解释应当很好邃晓了,总结就是做了一下两件事:
1.起首会将ex. ${student.name} 转成 student.name
2.将student.name经由过程 tring propVal = placeholderResolver.resolvePlaceholder(placeholder);猎取真正的值然后返回。
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
该要领就比较简朴了就是依据差别的形式做处置惩罚,systemPropertiesMode默许是SYSTEM_PROPERTIES_MODE_FALLBACK
- 假如systemPropertiesMode = SYSTEM_PROPERTIES_MODE_OVERRIDE 是指 体系设置文件优先级大于我们的设置文件。
- 拿不到或许设置不等于SYSTEM_PROPERTIES_MODE_OVERRIDE 就去我们设置文件里举行猎取
- 假如还猎取不到且假如systemPropertiesMode=SYSTEM_PROPERTIES_MODE_FALLBACK就再尝试去体系文件里查找。
结论
经由以上各个步骤终究BeanDefinition里的parentName beanClassName property中的占位符都邑被我们propertis设置文件中对应的值所替代掉,这就为后续实例化bean后做bean实例属性添补时做好了预备。
我们再举行 Student student =(Student) ac.getBean("student"); 时就能够一般打印对应的值了。
看源码的一点分享tip
- 看源码可能会比较死板,对峙很主要,假如看一遍是懵的那就是歇一歇再继承多看几遍如许逐步就会找到觉得
- 看的过程当中肯定要写例子举行调试,然后再继承重复看逐步就会邃晓个中的寄义
- 看源码能够看某一个中心类的继承图,以及javadoc。还能够尝试画出中心的时序图,这些都能对我们看懂源码起到事半功倍的作用。
Golang实现高性能凑单工具:给定<金额列表>计算<目标金额>所有组合