SpringBoot启动流程

SpringBoot核心启动类的SpringApplication。从SpringApplication.run()开始先创建SpringApplication对象,并调用该对象的run方法。

public static ConfigurableApplicationContext run(Object source, String... args) {
    return run(new Object[] { source }, args);
}

public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
    return new SpringApplication(sources).run(args);//在这个静态方法中,创建SpringApplication对象,并调用该对象的run方法。
}

构建SpringApplication对象

初始化SpringApplication的source

public SpringApplication(Object... sources) {
    initialize(sources);
}

//初始化SpringApplication对象的成员变量sources,webEnvironment,initializers,listeners,mainApplicationClass。
//sources的赋值比较简单,就是我们传给SpringApplication.run方法的参数。
private void initialize(Object[] sources) {
    // 为成员变量sources赋值
    if (sources != null && sources.length > 0) {
        this.sources.addAll(Arrays.asList(sources));
    }
    this.webEnvironment = deduceWebEnvironment();
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    this.mainApplicationClass = deduceMainApplicationClass();
}

初始化SpringApplication的webEnvironment

private boolean webEnvironment; 

private static final String[] WEB_ENVIRONMENT_CLASSES = { 
  "javax.servlet.Servlet",
  "org.springframework.web.context.ConfigurableWebApplicationContext"
};
//可以看到webEnvironment是一个boolean,该成员变量用来表示当前应用程序是不是一个Web应用程序。
//那么怎么决定当前应用程序是否Web应用程序呢,是通过在classpath中查看是否存在WEB_ENVIRONMENT_CLASSES这个数组中所包含的类,
//如果存在那么当前程序即是一个Web应用程序,反之则不然。
private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false; } } return true; }

初始化SpringApplication的initializers

//initializers成员变量,是一个ApplicationContextInitializer类型对象的集合
private List<ApplicationContextInitializer<?>> initializers;
public void setInitializers(
        Collection<? extends ApplicationContextInitializer<?>> initializers) {
    this.initializers = new ArrayList<ApplicationContextInitializer<?>>();
    this.initializers.addAll(initializers);
}

private void initialize(Object[] sources) {
    ...
    // 为成员变量initializers赋值
    //关键是调用getSpringFactoriesInstances(ApplicationContextInitializer.class),来获取ApplicationContextInitializer类型对象的列表。
    setInitializers((Collection) getSpringFactoriesInstances(
            ApplicationContextInitializer.class));
    ...
}
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,
//然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。
//最后会将创建好的对象列表排序并返回。
//可以看到,是从一个名字叫spring.factories的资源文件中,读取key为org.springframework.context.ApplicationContextInitializer的value。 private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<String>( SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; } public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) { String factoryClassName = factoryClass.getName(); try { Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); List<String> result = new ArrayList<String>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url)); String factoryClassNames = properties.getProperty(factoryClassName); result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames))); } return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<T>(names.size()); for (String name : names) { try { Class<?> instanceClass = ClassUtils.forName(name, classLoader); Assert.isAssignable(type, instanceClass); Constructor<?> constructor = instanceClass.getConstructor(parameterTypes); T instance = (T) constructor.newInstance(args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException( "Cannot instantiate " + type + " : " + name, ex); } } return instances; } spring.factories的部分内容如下: # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.context.web.ServerPortInfoApplicationContextInitializer

初始化SpringApplication的listeners

//listeners成员变量,是一个ApplicationListener<?>类型对象的集合
private List<ApplicationListener<?>> listeners;
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
    this.listeners = new ArrayList<ApplicationListener<?>>();
    this.listeners.addAll(listeners);
}

private void initialize(Object[] sources) {
    ...
    // 为成员变量listeners赋值
    //可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,
  //只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); ... } spring.factories中的相关内容: # Application Listeners org.springframework.context.ApplicationListener=\ org.springframework.boot.builder.ParentContextCloserApplicationListener,\ org.springframework.boot.context.FileEncodingApplicationListener,\ org.springframework.boot.context.config.AnsiOutputApplicationListener,\ org.springframework.boot.context.config.ConfigFileApplicationListener,\ org.springframework.boot.context.config.DelegatingApplicationListener,\ org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\ org.springframework.boot.logging.ClasspathLoggingApplicationListener,\ org.springframework.boot.logging.LoggingApplicationListener

初始化SpringApplication的mainApplicationClass

private Class<?> mainApplicationClass;
private void initialize(Object[] sources) {
    ...
    // 为成员变量mainApplicationClass赋值
    this.mainApplicationClass = deduceMainApplicationClass();
    ...
}
//通过获取当前调用栈,找到入口方法main所在的类,并将其复制给SpringApplication对象的成员变量mainApplicationClass。
private Class<?> deduceMainApplicationClass() {
    try {
        StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
        for (StackTraceElement stackTraceElement : stackTrace) {
            if ("main".equals(stackTraceElement.getMethodName())) {
                return Class.forName(stackTraceElement.getClassName());
            }
        }
    }
    catch (ClassNotFoundException ex) {
        // Swallow and continue
    }
    return null;
}

启动SpringApplication

经过上面的初始化过程,我们已经有了一个SpringApplication对象,根据SpringApplication类的静态run方法一节中的分析,接下来会调用SpringApplication对象的run方法。我们接下来就分析这个对象的run方法。

//可变个数参数args即是我们整个应用程序的入口main方法的参数
//StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。
//SpringApplication对象的run方法创建并刷新ApplicationContext
public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    configureHeadlessProperty();
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.started();
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);
        afterRefresh(context, applicationArguments);
        listeners.finished(context, null);
        stopWatch.stop();
        if (this.logStartupInfo) {
            new StartupInfoLogger(this.mainApplicationClass)
                    .logStarted(getApplicationLog(), stopWatch);
        }
        return context;
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, ex);
        throw new IllegalStateException(ex);
    }
}

headless模式

//实际上是就是设置系统属性java.awt.headless,一般运行在没有显示器和键盘的环境时设置为true
private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";
private boolean headless = true;

public ConfigurableApplicationContext run(String... args) {
    ...
    //设置headless模式
        configureHeadlessProperty();
    ...
}
private void configureHeadlessProperty() {
    System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, System.getProperty(
            SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

获取listeners

//run方法中,加载了一系列SpringApplicationRunListener对象,
//在创建和更新ApplicationContext方法前后分别调用了listeners对象的started方法和finished方法,
//并在创建和刷新ApplicationContext时,将listeners作为参数传递到了createAndRefreshContext方法中,
//以便在创建和刷新ApplicationContext的不同阶段,调用listeners的相应方法以执行操作。
//所以,所谓的SpringApplicationRunListeners实际上就是在SpringApplication对象的run方法执行的不同阶段,去执行一些操作,并且这些操作是可配置的。
public ConfigurableApplicationContext run(String... args) { ... SpringApplicationRunListeners listeners = getRunListeners(args); listeners.started(); /** * 创建并刷新ApplicationContext * context = createAndRefreshContext(listeners, applicationArguments); **/ listeners.finished(context, null); ... } //同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。
//那么加载了什么,就可以从spring.factories文件中看到了:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); } # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener

创建并刷新ApplicationContext

//首先是创建一个DefaultApplicationArguments对象,
//之后调用createAndRefreshContext方法创建并刷新一个ApplicationContext,
//最后调用afterRefresh方法在刷新之后做一些操作。
public ConfigurableApplicationContext run(String... args) {
    ...
    try {
        ApplicationArguments applicationArguments = new DefaultApplicationArguments(
                args);
        context = createAndRefreshContext(listeners, applicationArguments);
        afterRefresh(context, applicationArguments);
        ...
    }
    catch (Throwable ex) {
        handleRunFailure(context, listeners, ex);
        throw new IllegalStateException(ex);
    }
}
//创建一个DefaultApplicationArguments对象
DefaultApplicationArguments(String[] args) {
    Assert.notNull(args, "Args must not be null");
    this.source = new Source(args);
    this.args = args;
}
private static class Source extends SimpleCommandLinePropertySource {
    Source(String[] args) {
        super(args);
    }
    ...
}
public SimpleCommandLinePropertySource(String... args) {
    super(new SimpleCommandLineArgsParser().parse(args));
}
//createAndRefreshContext方法创建并刷新一个ApplicationContext
private ConfigurableEnvironment environment;
private boolean webEnvironment;
private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;
    // 创建并配置Environment
    ConfigurableEnvironment environment = getOrCreateEnvironment();
    configureEnvironment(environment, applicationArguments.getSourceArgs());
    listeners.environmentPrepared(environment);//发送事件
    if (isWebEnvironment(environment) && !this.webEnvironment) {
        environment = convertToStandardEnvironment(environment);
    }
    ...
    return context;
}
//Spring Application的Environment代表着程序运行的环境,
//主要包含了两种信息,一种是profiles,用来描述哪些bean definitions是可用的;
//一种是properties,用来描述系统的配置,其来源可能是配置文件、JVM属性文件、操作系统环境变量等等
private ConfigurableEnvironment getOrCreateEnvironment() {
  //首先要调用getOrCreateEnvironment方法获取一个Environment对象
  //执行到此处时,environment成员变量为null,而webEnvironment成员变量的值为true,所以会创建一个StandardServletEnvironment对象并返回
if (this.environment != null) { 
       return this.environment;    
   }    
  if (this.webEnvironment) {
return new StandardServletEnvironment();
    }
return new StandardEnvironment();
}
private Map<String, Object> defaultProperties;
private boolean addCommandLineProperties = true;
private Set<String> additionalProfiles = new HashSet<String>();
//调用configureEnvironment方法来配置上一步获取的Environment对象.
protected void configureEnvironment(ConfigurableEnvironment environment,
        String[] args) {
  //configureEnvironment方法先是调用configurePropertySources来配置properties,然后调用configureProfiles来配置profiles
    configurePropertySources(environment, args);
    configureProfiles(environment, args);
}
protected void configurePropertySources(ConfigurableEnvironment environment,
        String[] args) {
    MutablePropertySources sources = environment.getPropertySources();
  //configurePropertySources首先查看SpringApplication对象的成员变量defaultProperties
    if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
    //如果该变量非null且内容非空,则将其加入到Environment的PropertySource列表的最后。
        sources.addLast(
                new MapPropertySource("defaultProperties", this.defaultProperties));
    }
  //然后查看SpringApplication对象的成员变量addCommandLineProperties和main函数的参数args
    if (this.addCommandLineProperties && args.length > 0) {
    //如果设置了addCommandLineProperties=true,且args个数大于0
       //那么就构造一个由main函数的参数组成的PropertySource放到Environment的PropertySource列表的最前面
    //(这就能保证,我们通过main函数的参数来做的配置是最优先的,可以覆盖其他配置) 
    String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
        if (sources.contains(name)) {
            PropertySource<?> source = sources.get(name);
            CompositePropertySource composite = new CompositePropertySource(name);
            composite.addPropertySource(new SimpleCommandLinePropertySource(
                    name + "-" + args.hashCode(), args));
            composite.addPropertySource(source);
            sources.replace(name, composite);
        }
        else {
            sources.addFirst(new SimpleCommandLinePropertySource(args));
        }
    }
}
//configureProfiles首先会读取Properties中key为spring.profiles.active的配置项,配置到Environment
//然后再将SpringApplication对象的成员变量additionalProfiles加入到Environment的active profiles配置中
//配置文件里没有spring.profiles.active的配置项
//而SpringApplication对象的成员变量additionalProfiles也是一个空的集合,所以这个函数没有配置任何active profile
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
    environment.getActiveProfiles(); // ensure they are initialized
    // But these ones should go first (last wins in a property key clash)
    Set<String> profiles = new LinkedHashSet<String>(this.additionalProfiles);
    profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
    environment.setActiveProfiles(profiles.toArray(new String[profiles.size()]));
}
//Environment就算是配置完成了.
//接下来调用SpringApplicationRunListeners类的对象listeners发布ApplicationEnvironmentPreparedEvent事件.然后listener进行各种响应。
//省略各个listener响应逻辑
//打印banner,printBanner方法中, //首先会调用selectBanner方法得到一个banner对象, //然后判断bannerMode的类型,如果是Banner.Mode.LOG,那么将banner对象转换为字符串,打印一条info日志, //否则的话,调用banner对象的printbanner方法,将banner打印到标准输出System.out。 private Banner banner; private Banner.Mode bannerMode = Banner.Mode.CONSOLE; public static final String BANNER_LOCATION_PROPERTY = "banner.location"; public static final String BANNER_LOCATION_PROPERTY_VALUE = "banner.txt"; private static final Banner DEFAULT_BANNER = new SpringBootBanner(); private ConfigurableApplicationContext createAndRefreshContext( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ... if (this.bannerMode != Banner.Mode.OFF) { printBanner(environment); } ... } protected void printBanner(Environment environment) { Banner selectedBanner = selectBanner(environment); if (this.bannerMode == Banner.Mode.LOG) { try { logger.info(createStringFromBanner(selectedBanner, environment)); } catch (UnsupportedEncodingException ex) { logger.warn("Failed to create String for banner", ex); } } else { selectedBanner.printBanner(environment, this.mainApplicationClass, System.out); } } private Banner selectBanner(Environment environment) { String location = environment.getProperty(BANNER_LOCATION_PROPERTY, BANNER_LOCATION_PROPERTY_VALUE); ResourceLoader resourceLoader = this.resourceLoader != null ? this.resourceLoader : new DefaultResourceLoader(getClassLoader()); Resource resource = resourceLoader.getResource(location); if (resource.exists()) { return new ResourceBanner(resource); } if (this.banner != null) { return this.banner; } return DEFAULT_BANNER; } private String createStringFromBanner(Banner banner, Environment environment) throws UnsupportedEncodingException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); banner.printBanner(environment, this.mainApplicationClass, new PrintStream(baos)); String charset = environment.getProperty("banner.charset", "UTF-8"); return baos.toString(charset); } //bannerMode是Banner.Mode.Console,而且也不曾提供过banner.txt这样的资源文件。 //所以selectBanner方法中得到到便是默认的banner对象,即SpringBootBanner类的对象: //先打印个Spring的图形,然后打印个Spring Boot的文本,再然后打印一下Spring Boot的版本。 private static final String[] BANNER = { "", " . ____ _ __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/" }; private static final String SPRING_BOOT = " :: Spring Boot :: "; private static final int STRAP_LINE_SIZE = 42; @Override public void printBanner(Environment environment, Class<?> sourceClass, PrintStream printStream) { for (String line : BANNER) { printStream.println(line); } String version = SpringBootVersion.getVersion(); version = (version == null ? "" : " (v" + version + ")"); String padding = ""; while (padding.length() < STRAP_LINE_SIZE - (version.length() + SPRING_BOOT.length())) { padding += " "; } printStream.println(AnsiOutput.toString(AnsiColor.GREEN, SPRING_BOOT, AnsiColor.DEFAULT, padding, AnsiStyle.FAINT, version)); printStream.println(); }

 

//创建ApplicationContext
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
        + "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework."
        + "boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";

private ConfigurableApplicationContext createAndRefreshContext(
        SpringApplicationRunListeners listeners,
        ApplicationArguments applicationArguments) {
    ConfigurableApplicationContext context;
    ...
    //创建ApplicationContext对象
    context = createApplicationContext();
    //将我们之前准备好的Environment对象赋值进去
    context.setEnvironment(environment);
    //调用postProcessApplicationContext和applyInitializers做一些处理和初始化的操作
    postProcessApplicationContext(context);
    applyInitializers(context);
    listeners.contextPrepared(context);
    if (this.logStartupInfo) {
        logStartupInfo(context.getParent() == null);
        logStartupProfileInfo(context);
    }
    ...
    return context;
}

protected ConfigurableApplicationContext createApplicationContext() {
    Class<?> contextClass = this.applicationContextClass;
    if (contextClass == null) {
        try {
            contextClass = Class.forName(this.webEnvironment
                    ? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Unable create a default ApplicationContext, "
                            + "please specify an ApplicationContextClass",
                    ex);
        }
    }
    return (ConfigurableApplicationContext) 
//调用BeanUtils的instantiate方法来创建ApplicationContext对象
BeanUtils.instantiate(contextClass);
}
//通过调用Class对象的newInstance()方法来实例化对象,这等同于直接调用类的空的构造方法
public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
    Assert.notNull(clazz, "Class must not be null");
    if (clazz.isInterface()) {
        throw new BeanInstantiationException(clazz, "Specified class is an interface");
    }
    try {
        return clazz.newInstance();
    }
    catch (InstantiationException ex) {
        throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
    }
    catch (IllegalAccessException ex) {
        throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
    }
}
//构造方法中初始化了两个成员变量,
//类型分别为AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner
//用以加载使用注解的bean定义。
public AnnotationConfigEmbeddedWebApplicationContext() {
    this.reader = new AnnotatedBeanDefinitionReader(this);
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

@Override
public void setEnvironment(ConfigurableEnvironment environment) {
    super.setEnvironment(environment);
    this.reader.setEnvironment(environment);
    this.scanner.setEnvironment(environment);
}
//如果成员变量beanNameGenerator不为Null,那么为ApplicationContext对象注册beanNameGenerator bean。
//如果成员变量resourceLoader不为null,则为ApplicationContext对象设置ResourceLoader。
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
    if (this.webEnvironment) {
        if (context instanceof ConfigurableWebApplicationContext) {
            ConfigurableWebApplicationContext configurableContext = (ConfigurableWebApplicationContext) context;
            if (this.beanNameGenerator != null) {
                configurableContext.getBeanFactory().registerSingleton(
                        AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
                        this.beanNameGenerator);
            }
        }
    }
    if (this.resourceLoader != null) {
        if (context instanceof GenericApplicationContext) {
            ((GenericApplicationContext) context)
                    .setResourceLoader(this.resourceLoader);
        }
        if (context instanceof DefaultResourceLoader) {
            ((DefaultResourceLoader) context)
                    .setClassLoader(this.resourceLoader.getClassLoader());
        }
    }
}
protected void applyInitializers(ConfigurableApplicationContext context) {
    for (ApplicationContextInitializer initializer : getInitializers()) {
        Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
                initializer.getClass(), ApplicationContextInitializer.class);
        Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
        initializer.initialize(context);
    }
}

public Set<ApplicationContextInitializer<?>> getInitializers() {
    return asUnmodifiableOrderedSet(this.initializers);
}

private static <E> Set<E> asUnmodifiableOrderedSet(Collection<E> elements) {
    List<E> list = new ArrayList<E>();
    list.addAll(elements);
    Collections.sort(list, AnnotationAwareOrderComparator.INSTANCE);
    return new LinkedHashSet<E>(list);
}

 

posted @ 2016-10-22 11:04  wade&luffy  阅读(1604)  评论(0编辑  收藏  举报