IoC容器的初始化包括BeanDefinition的Resource定位、载入和注册这三个基本的过程。

一、Resource定位。BeanDefinition的资源定位有resourceLoader通过统一的Resource接口来完成,这个Resource对各种形式的BeanDefinition的使用提供了统一接口。对于这些BeanDefinition的存在形式,相信不陌生,如:

FileSystemResource、ClassPathResource。这个过程类似于容器寻找数据的过程,就像用水桶装水要把水找到一样。

二、第二个关键的部分是BeanDefinition的载入,该载入过程把用户定义好的Bean表示成IoC容器内部的数据结构,而这个容器内部的数据结构就是BeanDefinition。简单说,BeanDefinition实际上是POJO对象在IoC容器中的抽象,这个BeanDefinition定义了一系列的数据来使得IoC容器能够方便地对POJO对象也就是Spring的Bean进行管理。即BeanDefinition就是Spring的领域对象。

三、第三个过程是向IoC容器注册这些BeanDefinition的过程。这个过程是通过调用BeanDefinitionRegistry接口的实现来完成的,这个注册过程把载入过程中解析得到的BeanDefinition向IoC容器进行注册。可以看到,在IoC容器内部,是通过使用一个HashMap来持有BeanDefinition数据的。

总结:

此处对于BeanPostProcessor接口的调用应该属于高级应用了,该思路常用来解决扩展或集成Spring框架,其核心的思路可以分为以下几步:

1、自定义实现类路径扫描类,决定哪些类应该被注入进Spring容器。

2、采用Java动态代理来动态实现对于声明接口类的注入。

3、实现BeanDefinitionRegistryPostProcessor,在Spring初始化初期将需要扫描导入Spring容器的类进行注入。

 4、通过代码动态创建

代码动态创建

我们通过getBean来获得对象,但这些对象都是事先定义好的,我们有时候要在程序中动态的加入对象.因为如果采用配置文件或者注解,我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就得采用动态处理bean,包括:动态注入,动态删除。

本节大纲 :
(1)动态注入bean思路;
(2)动态注入实现代码;
(3)多次注入同一个bean的情况;
(4)动态删除;

接下来我们看下具体的内容:

(1)动态注入bean思路;

在具体进行代码实现的时候,我们要知道,Spring管理bean的对象是BeanFactory,具体的是DefaultListableBeanFactory,在这个类当中有一个注入bean的方法:registerBeanDefinition,在调用registerBeanDefinition方法时,需要BeanDefinition参数,那么这个参数怎么获取呢?Spring提供了BeanDefinitionBuilder可以构建一个BeanDefinition,那么我们的问题就是如何获取BeanFactory了,这个就很简单了,只要获取到ApplicationContext对象即可获取到BeanFacory了。

(2)动态注入实现代码;

综上所述,如果我们要编写一个简单里的例子的话,那么分以个几个步骤进行编码即可进行动态注入了:

<1>. 获取ApplicationContext;
<2>. 通过ApplicationContext获取到BeanFacotory;
<3>. 通过BeanDefinitionBuilder构建BeanDefiniton;
<4>. 调用beanFactory的registerBeanDefinition注入beanDefinition;
<5>. 使用ApplicationContext.getBean获取bean进行测试;

很明显我们需要先定义个类进行测试,比如TestService代码如下:

package com.dxz.test;

public class TestService {

    private String name;

    public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public void print() {
System.out.println("动态载入bean,name=" + name);
} }

注意:这里没有使用@Service和配置文件进行注入TestService。

那么下面我们的目标就是动态注入TestService了,根据以上的分析,我们进行编码,具体代码如下:

package com.dxz.test;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext; @SpringBootApplication
public class Application { public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args); // 获取BeanFactory
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) ctx
.getAutowireCapableBeanFactory(); // 创建bean信息.
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name", "张三"); // 动态注册bean.
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition()); // 获取动态注册的bean.
TestService testService = ctx.getBean(TestService.class);
testService.print();
}
}

或者

@SpringBootApplication
public class Application { public static void main(String[] args) {
ClassPathResource res = new ClassPathResource("beans.xml"); // 获取BeanFactory
DefaultListableBeanFactory factory= new DefaultListableBeanFactory(); // 创建bean信息.
XmlBeanDefinitionReader beanDefinitionBuilder = new XmlBeanDefinitionReader(factory);
beanDefinitionBuilder.loadBeanDefinitions(res);
//...
}
}

执行代码我们会在控制台看到如下打印信息:

动态载入bean,name=张三

到这里,就证明我们的代码很成功了。

(3)多次注入同一个bean的情况;

多次注入同一个bean的,如果beanName不一样的话,那么会产生两个Bean;如果beanName一样的话,后面注入的会覆盖前面的。

第一种情况:beanName一样的代码:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService", beanDefinitionBuilder.getBeanDefinition());

运行看控制台:

动态载入bean,name=李四

第二种情况:beanName不一样的代码:

beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestService.class);
beanDefinitionBuilder.addPropertyValue("name","李四");
defaultListableBeanFactory.registerBeanDefinition("testService1",beanDefinitionBuilder.getBeanDefinition());

此时如果没有更改别的代码直接运行的话,是会报如下错误的:

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.kfit.demo.service.TestService] is defined: expected single matching bean but found 2: testService1,testService

大体意思就是在getBean的时候,找到了两个bean,这时候就不知道要获取哪个了,所以在获取的时候,我们就要指定我们是要获取的testService还是testService1,只需要修改一句代码:

将代码:

TestService testService =ctx.getBean(TestService.class);
修改为:
TestService testService =ctx.getBean("testService");

(4)动态删除;

相对于动态注入,动态删除就很简单了,直接奉上代码:

//删除bean.
defaultListableBeanFactory.removeBeanDefinition("testService");

实现BeanDefinitionRegistryPostProcessor,在Spring初始化初期将需要扫描导入Spring容器的类进行注入

"对于Spring框架,现实公司使用的非常广泛,但是由于业务的复杂程度不同,了解到很多小伙伴们利用Spring开发仅仅是利用了Spring的IOC,即使是AOP也很少用,但是目前的Spring是一个大家族,形成了一个很大的生态,覆盖了我们平时开发的方方面面,抛开特殊的苛刻要求之外,Spring的生态其实已经很全面了,所以在此开个系列来研究下Spring提供给我们的一些平时不太却又很实用的内容。"

上一篇我们分析了BeanPostProcessor的基本使用,接下来我们分析下如何使用该类实现动态的接口注入,示例说明:在BeetlSQL框架中,在使用自动扫描注入时,我们通常只需要配置上要扫描的包路径,然后在该路径下声明对应的Dao接口类,这些接口类都默认继承BaseMapper接口类,然后我们在使用这些Dao类的时候,直接根据类型注入(@Autowired)即可使用,这个其实和Mybatis的那一套相似,也和Spring自身的Spring-data框架也类似。这个经常用于框架的开发,那么我就该部分的实现做相应的解释,这三个框架具体实现可能有差距,感兴趣的小伙伴自行去查看源码,我会以一个很简单的例子来讲解大概的实现逻辑。

问题描述:

继承Spring框架,实现声明某个自定义接口(UserMapper),改接口继承通用接口BaseMapper,(通用接口BaseMapper有默认的实现类),实现通过类型注入UserMapper类,然后通过Spring框架的上下文类(ApplicationContext实现类)的getBean()方法拿到UserMapper类来调用内部提供的方法。

1、声明BaseMapper接口类

package com.dxz.test;
public interface BaseMapper { public void add(String value); public void remove(String key);
} public class CustomBaseMapper implements BaseMapper {
private final Logger logger = Logger.getLogger(this.getClass().getName());
private List<String> dataList = new CopyOnWriteArrayList<>(); @Override
public void add(String value) {
logger.info("添加数据:" + value);
dataList.add(value);
} @Override
public void remove(String key) {
if (dataList.isEmpty())
throw new IllegalArgumentException("Can't remove because the list is Empty!");
}
}

接下来是继承Spring的核心代码

3、首先我们要先定义一个扫描某路径下的类,该类继承ClassPathBeanDefinitionScanner,自定义扫描类:DefaultClassPathScanner

package com.dxz.test;

import java.io.IOException;
import java.util.Arrays;
import java.util.Set; import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.TypeFilter; public class DefaultClassPathScanner extends ClassPathBeanDefinitionScanner { private final String DEFAULT_MAPPER_SUFFIX = "Mapper"; public DefaultClassPathScanner(BeanDefinitionRegistry registry) {
super(registry, false);
} private String mapperManagerFactoryBean; /**
* 扫描包下的类-完成自定义的Bean定义类
*
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
// 如果指定的基础包路径中不存在任何类对象,则提示
if (beanDefinitions.isEmpty()) {
logger.warn("系统没有在 '" + Arrays.toString(basePackages) + "' 包中找到任何Mapper,请检查配置");
} else {
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
} /**
* 注册过滤器-保证正确的类被扫描注入
*/
protected void registerFilters() {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
// TODO 这里设置包含条件-此处是个扩展点,可以根据自定义的类后缀过滤出需要的类
return className.endsWith(DEFAULT_MAPPER_SUFFIX);
}
});
addExcludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory)
throws IOException {
String className = metadataReader.getClassMetadata().getClassName();
return className.endsWith("package-info");
}
});
} /**
* 重写父类的判断是否能够实例化的组件-该方法是在确认是否真的是isCandidateComponent 原方法解释:
* 确定给定的bean定义是否有资格成为候选人。 默认实现检查类是否不是接口,也不依赖于封闭类。 以在子类中重写。
*
* @param beanDefinition
* @return
*/
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// 原方法这里是判断是否为顶级类和是否是依赖类(即接口会被排除掉-由于我们需要将接口加进来,所以需要覆盖该方法)
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
} /**
* 扩展方法-对扫描到的含有BeetlSqlFactoryBean的Bean描述信息进行遍历
*
* @param beanDefinitions
*/
void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String mapperClassName = definition.getBeanClassName();
// 必须在这里加入泛型限定,要不然在spring下会有循环引用的问题
definition.getConstructorArgumentValues().addGenericArgumentValue(mapperClassName);
// 依赖注入
definition.getPropertyValues().add("mapperInterface", mapperClassName);
// 根据工厂的名称创建出默认的BaseMapper实现
definition.getPropertyValues().add("mapperManagerFactoryBean",
new RuntimeBeanReference(this.mapperManagerFactoryBean));
definition.setBeanClass(BaseMapperFactoryBean.class);
// 设置Mapper按照接口组装
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
logger.info("已开启自动按照类型注入 '" + holder.getBeanName() + "'.");
}
} public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}

4、核心的接口实现类:BaseMapperFactoryBean

package com.dxz.test;

import java.lang.reflect.Proxy;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; public class BaseMapperFactoryBean<T>
implements FactoryBean<T>, InitializingBean, ApplicationListener<ApplicationEvent>, ApplicationContextAware {
/**
* 要注入的接口类定义
*/
private Class<T> mapperInterface; /**
* Spring上下文
*/
private ApplicationContext applicationContext; // 也因该走工厂方法注入得来 private BaseMapper mapperManagerFactoryBean; public BaseMapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} @Override
public T getObject() throws Exception {
// 采用动态代理生成接口实现类,核心实现
return (T) Proxy.newProxyInstance(applicationContext.getClassLoader(), new Class[] { mapperInterface },
new MapperJavaProxy(mapperManagerFactoryBean, mapperInterface));
} @Override
public Class<?> getObjectType() {
return this.mapperInterface;
} @Override
public boolean isSingleton() {
return true;
} @Override
public void afterPropertiesSet() throws Exception {
// TODO 判断属性的注入是否正确-如mapperInterface判空
if (null == mapperInterface)
throw new IllegalArgumentException("Mapper Interface Can't Be Null!!");
} /**
* Handle an application event.
*
* @param event
* the event to respond to
*/
@Override
public void onApplicationEvent(ApplicationEvent event) {
// TODO 可依据事件进行扩展
} @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
} public void setMapperInterface(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public void setMapperManagerFactoryBean(BaseMapper mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
}
}

5、定义默认的BaseMapper的FactoryBean-MapperManagerFactoryBean

package com.dxz.test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; public class MapperJavaProxy implements InvocationHandler { private BaseMapper baseMapper; private Class<?> interfaceClass; public MapperJavaProxy(BaseMapper baseMapper, Class<?> interfaceClass) {
this.baseMapper = baseMapper;
this.interfaceClass = interfaceClass;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException("mapperInterface is not interface.");
} if (baseMapper == null) {
baseMapper = new CustomBaseMapper();
}
return method.invoke(baseMapper, args);
}
}

7、调用时的核心配置类:DefaultClassRegistryBeanFactory

package com.dxz.test;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext; public class DefaultClassRegistryBeanFactory
implements ApplicationContextAware, BeanDefinitionRegistryPostProcessor, BeanNameAware { private String scanPackage; private String beanName; private String mapperManagerFactoryBean; private ApplicationContext applicationContext; public String getScanPackage() {
return scanPackage;
} public void setScanPackage(String scanPackage) {
this.scanPackage = scanPackage;
} public String getMapperManagerFactoryBean() {
return mapperManagerFactoryBean;
} public void setMapperManagerFactoryBean(String mapperManagerFactoryBean) {
this.mapperManagerFactoryBean = mapperManagerFactoryBean;
} @Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
if (StringUtils.isEmpty(this.scanPackage)) {
throw new IllegalArgumentException("scanPackage can't be null");
}
String basePackage2 = this.applicationContext.getEnvironment().resolvePlaceholders(this.scanPackage);
String[] packages = StringUtils.tokenizeToStringArray(basePackage2,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
DefaultClassPathScanner defaultClassPathScanner = new DefaultClassPathScanner(beanDefinitionRegistry);
defaultClassPathScanner.setMapperManagerFactoryBean(mapperManagerFactoryBean);
defaultClassPathScanner.registerFilters(); defaultClassPathScanner.doScan(packages);
} @Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
throws BeansException { } @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
} @Override
public void setBeanName(String name) {
this.beanName = name;
}
}

8、调用测试

8.1、假设你在包目录:colin.spring.basic.advanced.inject.dao下声明自定义的类UserMapper

public interface UserMapper extends BaseMapper {
}

8.2、声明配置类:ClassRegistryBeanScannerConfig

package com.dxz.test;

@Configuration
public class ClassRegistryBeanScannerConfig { @Bean(name = "mapperManagerFactoryBean")
public MapperManagerFactoryBean configMapperManagerFactoryBean() {
MapperManagerFactoryBean mapperManagerFactoryBean = new MapperManagerFactoryBean();
return mapperManagerFactoryBean;
} @Bean
public DefaultClassRegistryBeanFactory configDefaultClassRegistryBeanFactory() {
DefaultClassRegistryBeanFactory defaultClassRegistryBeanFactory = new DefaultClassRegistryBeanFactory();
defaultClassRegistryBeanFactory.setScanPackage("colin.spring.basic.advanced.inject.dao");
defaultClassRegistryBeanFactory.setMapperManagerFactoryBean("mapperManagerFactoryBean");
return defaultClassRegistryBeanFactory;
}
}

8.3、测试调用

package com.dxz.test;

public class Snippet {
public static void main(String[] args) {
AnnotationConfigApplicationContext acApplicationCOntext = new AnnotationConfigApplicationContext("colin.spring.basic.advanced.inject");
UserMapper userMapper = acApplicationCOntext.getBean(UserMapper.class);
userMapper.add("lalaldsf");
acApplicationCOntext.stop();
}
}

Spring之2:Spring Bean动态注册、删除的更多相关文章

  1. 品Spring:SpringBoot发起bean定义注册的“二次攻坚战”

    上一篇文章整体非常轻松,因为在容器启动前,只注册了一个bean定义,就是SpringBoot的主类. OK,今天接着从容器的启动入手,找出剩余所有的bean定义的注册过程. 具体细节肯定会颇为复杂,同 ...

  2. Spring 通过@Import实现Bean的注册

    今天看到一个神奇的用法, Spring可以通过@Import导入实现了ImportBeanDefinitionRegistrar接口的类来注册那个类. ImportBeanDefinitionRegi ...

  3. 【Spring】bean动态注册到spring

    /* * http://412887952-qq-com.iteye.com/blog/2348445 * http://www.jb51.net/article/106558.htm * https ...

  4. Spring工厂方式创建Bean实例

    创建Bean实例的方式: 1) 通过构造器(有参或无参) 方式: <bean id="" class=""/> 2) 通过静态工厂方法 方式: &l ...

  5. Spring Boot动态注入删除bean

    Spring Boot动态注入删除bean 概述 因为如果采用配置文件或者注解,我们要加入对象的话,还要重启服务,如果我们想要避免这一情况就得采用动态处理bean,包括:动态注入,动态删除. 动态注入 ...

  6. spring中注册bean(通过代码动态注册)

    看公司的源代码,在一个类中使用到了BeanDefinitionBuilder这个类,在学习之后才知道在项目中可能没有注册bean,在使用的时候才会进行注册,就涉及到了动态bean的注册,所以,在文章中 ...

  7. Spring动态注册bean实现动态多数据源

    Spring动态注册bean实现动态多数据源 http://blog.csdn.net/littlechang/article/details/8071882

  8. 使用Spring容器动态注册和获取Bean

    有时候需要在运行时动态注册Bean到Spring容器,并根据名称获取注册的Bean.比如我们自己的SAAS架构的系统需要调用ThingsBoard API和Thingsboard交互,就可以通过Thi ...

  9. 如何动态在spring mvc中增加bean

    阅读对象 搭框架人员,或者其他感兴趣的开发人员 背景 一般来说在业务代码中,加上 @Component, @Service,@Repository, @Controller等注解就可以实现将bean注 ...

随机推荐

  1. iOS应用公布Invalid Binary问题解决方式

    上次公布一淘HD应用.头一天公布,第二天就上线,私下还在想.是不是苹果採用什么优化的解决方式了,导致审核速度加快了. 这两天公布新版.一直碰到Invaild Binary问题.才想明确,原来大家都被这 ...

  2. ABAP下载xml文件

    [转http://www.cnblogs.com/byfhd/archive/2007/08/17/859829.html] ************************************* ...

  3. vs+opencv

    vs + opencv 配置见网页 https://blog.csdn.net/qq_17550379/article/details/78201442 统一所有工程配置见下图:

  4. androidAndroid开发学习--Ionic+Cordova 环境搭建

    我们看 Ionic 能给我们提供什么?  一个样式库,你可以使用它 来 装饰你的 HTML 网页 ,看起来 想 移动程序的 界面,什么 header .content.footer.grid.list ...

  5. Spring声明式事务管理与配置介绍

    转至:http://java.9sssd.com/javafw/art/1215 [摘要]本文介绍Spring声明式事务管理与配置,包括Spring声明式事务配置的五种方式.事务的传播属性(Propa ...

  6. 纯CSS3垂直动画菜单

    在线演示 本地下载

  7. 20165101 学习基础和C语言基础调查

    学习基础和C语言基础调查 技能学习心得 看了15级学长学姐丰富的技能之后,我感到很惭愧.我的课外技能可以说是很糟糕.唱歌的话,小时候还可以用假声唱一下,变声之后就是高音上不去,低音下不来.体育更是差劲 ...

  8. day4 内置函数 迭代器&生成器 yield总结 三元运算 闭包

    内置函数: 内置函数 # abs()返回一个数字的绝对值.如果给出复数,返回值就是该复数的模. b = -100 print(b) print(abs(b)) # all() 所有为真才为真,只要有一 ...

  9. java.io.InvalidClassException 异常解决, 实现Serializable接口的注意事项

    解决方案: 在类中显式指定 private static final long serialVersionUID = 42L; 类实现序列化接口, 进行序列化反序列化的时候, 抛出 java.io.I ...

  10. BZOJ 1010 [HNOI2008]玩具装箱toy:斜率优化dp

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010 题意: 有n条线段,长度分别为C[i]. 你需要将所有的线段分成若干组,每组中线段的 ...