Umgebung
Dieser Artikel basiert auf Spring Boot Version 1.3.3 und verwendet Spring-Boot-Starter-Web.
Nach Abschluss der Konfiguration wird der Code wie folgt geschrieben:
@SpringBootApplicationpublic class Application { public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}@RestControllerpublic class RootController { public static final String PATH_ROOT = "/"; @RequestMapping(PATH_ROOT) public String welcome() { return "Welcome!";
}
}Obwohl es nur wenige Codezeilen gibt, handelt es sich bereits um ein vollständiges Web Beim Zugriff auf die URL wird als Pfadteil „/“ die Zeichenfolge „Willkommen!“ zurückgegeben.
Erstens handelt es sich um einen ganz gewöhnlichen Java-Programmeintrag, eine statische Hauptmethode, die der Vereinbarung entspricht. In dieser Hauptmethode wird die statische Ausführungsmethode von SpringApplication aufgerufen und das Anwendungsklassenobjekt und die Parameterargumente der Hauptmethode als Parameter übergeben.
Dann gibt es eine RootController-Klasse, die zwei Spring-Annotationen verwendet. Wir verwenden diese Klasse nicht direkt in der Hauptmethode.
Statische Ausführungsmethode der SpringApplication-Klasse
以下代码摘自:org.springframework.boot.SpringApplicationpublic 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);
}Erstellen Sie in dieser statischen Methode ein SpringApplication-Objekt und rufen Sie die Ausführungsmethode des Objekts auf.
Konstruieren Sie das SpringApplication-Objekt
以下代码摘自:org.springframework.boot.SpringApplicationpublic SpringApplication(Object... sources) { initialize(sources);
}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();
}Rufen Sie die Initialisierungsmethode im Konstruktor auf, um die Mitgliedsvariablenquellen, webEnvironment, Initialisierer, Listener und mainApplicationClass der SpringApplication zu initialisieren Objekt. Die Zuweisung von Quellen ist relativ einfach, es handelt sich um den Parameter, den wir an die SpringApplication.run-Methode übergeben. Werfen wir einen Blick nacheinander auf die verbleibenden.
Die erste ist webEnvironment:
以下代码摘自:org.springframework.boot.SpringApplicationprivate boolean webEnvironment;
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet", "org.springframework.web.context.ConfigurableWebApplicationContext" };private void initialize(Object[] sources) {
... // 为成员变量webEnvironment赋值 this.webEnvironment = deduceWebEnvironment();
...
}private boolean deduceWebEnvironment() { for (String className : WEB_ENVIRONMENT_CLASSES) { if (!ClassUtils.isPresent(className, null)) { return false;
}
} return true;
}Sie können sehen, dass webEnvironment ein boolescher Wert ist und diese Mitgliedsvariable verwendet wird, um anzugeben, ob die aktuelle Anwendung ein Web ist Anwendung. Um festzustellen, ob die aktuelle Anwendung eine Webanwendung ist, prüfen Sie, ob die im Array WEB_ENVIRONMENT_CLASSES enthaltene Klasse im Klassenpfad vorhanden ist. Wenn sie vorhanden ist, ist das aktuelle Programm eine Webanwendung und umgekehrt.
Im Beispiel dieses Artikels ist der Wert von webEnvironment wahr.
Dann gibt es Initialisierer:
Die Membervariable initializers ist eine Sammlung von Objekten vom Typ ApplicationContextInitializer. Wie der Name schon sagt, ist ApplicationContextInitializer eine Schnittstelle, die zum Initialisieren von ApplicationContext verwendet werden kann.
以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationContextInitializer<?>> initializers;private void initialize(Object[] sources) {
... // 为成员变量initializers赋值 setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
...
}public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) { this.initializers = new ArrayList<ApplicationContextInitializer<?>>(); this.initializers.addAll(initializers);
}Wie Sie sehen können, besteht der Schlüssel darin, getSpringFactoriesInstances(ApplicationContextInitializer.class) aufzurufen, um die Liste der Objekte vom Typ ApplicationContextInitializer abzurufen.
以下代码摘自:org.springframework.boot.SpringApplicationprivate <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) { return getSpringFactoriesInstances(type, new Class<?>[] {});
}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;
}在该方法中,首先通过调用SpringFactoriesLoader.loadFactoryNames(type, classLoader)来获取所有Spring Factories的名字,然后调用createSpringFactoriesInstances方法根据读取到的名字创建对象。最后会将创建好的对象列表排序并返回。
以下代码摘自:org.springframework.core.io.support.SpringFactoriesLoaderpublic 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);
}
}可以看到,是从一个名字叫spring.factories的资源文件中,读取key为org.springframework.context.ApplicationContextInitializer的value。而spring.factories的部分内容如下:
以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/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
可以看到,最近的得到的,是ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的名字。
接下来会调用createSpringFactoriesInstances来创建ApplicationContextInitializer实例。
以下代码摘自:org.springframework.boot.SpringApplicationprivate <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;
}所以在我们的例子中,SpringApplication对象的成员变量initalizers就被初始化为,ConfigurationWarningsApplicationContextInitializer,ContextIdApplicationContextInitializer,DelegatingApplicationContextInitializer,ServerPortInfoApplicationContextInitializer这四个类的对象组成的list。
下图画出了加载的ApplicationContextInitializer,并说明了他们的作用。至于何时应用他们,且听后面慢慢分解。
接下来是成员变量listeners
以下代码摘自:org.springframework.boot.SpringApplicationprivate List<ApplicationListener<?>> listeners;private void initialize(Object[] sources) {
... // 为成员变量listeners赋值 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
...
}public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList<ApplicationListener<?>>(); this.listeners.addAll(listeners);
}listeners成员变量,是一个ApplicationListener>类型对象的集合。可以看到获取该成员变量内容使用的是跟成员变量initializers一样的方法,只不过传入的类型从ApplicationContextInitializer.class变成了ApplicationListener.class。
看一下spring.factories中的相关内容:
以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/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
也就是说,在我们的例子中,listener最终会被初始化为ParentContextCloserApplicationListener,FileEncodingApplicationListener,AnsiOutputApplicationListener,ConfigFileApplicationListener,DelegatingApplicationListener,LiquibaseServiceLocatorApplicationListener,ClasspathLoggingApplicationListener,LoggingApplicationListener这几个类的对象组成的list。
下图画出了加载的ApplicationListener,并说明了他们的作用。至于他们何时会被触发,等事件出现时,我们再说明。
最后是mainApplicationClass
以下代码摘自:org.springframework.boot.SpringApplicationprivate Class<?> mainApplicationClass;private void initialize(Object[] sources) {
... // 为成员变量mainApplicationClass赋值 this.mainApplicationClass = deduceMainApplicationClass();
...
}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;
}在deduceMainApplicationClass方法中,通过获取当前调用栈,找到入口方法main所在的类,并将其复制给SpringApplication对象的成员变量mainApplicationClass。在我们的例子中mainApplicationClass即是我们自己编写的Application类。
SpringApplication对象的run方法
经过上面的初始化过程,我们已经有了一个SpringApplication对象,根据SpringApplication类的静态run方法一节中的分析,接下来会调用SpringApplication对象的run方法。我们接下来就分析这个对象的run方法。
以下代码摘自:org.springframework.boot.SpringApplicationpublic 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);
}
}可变个数参数args即是我们整个应用程序的入口main方法的参数,在我们的例子中,参数个数为零。
StopWatch是来自org.springframework.util的工具类,可以用来方便的记录程序的运行时间。
SpringApplication对象的run方法创建并刷新ApplicationContext,算是开始进入正题了。下面按照执行顺序,介绍该方法所做的工作。
headless模式
以下代码摘自:org.springframework.boot.SpringApplicationprivate 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)));
}实际上是就是设置系统属性java.awt.headless,在我们的例子中该属性会被设置为true,因为我们开发的是服务器程序,一般运行在没有显示器和键盘的环境。关于java中的headless模式,更多信息可以参考这里。
SpringApplicationRunListeners
以下代码摘自:org.springframework.boot.SpringApplicationpublic ConfigurableApplicationContext run(String... args) {
...
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.started(); /** * 创建并刷新ApplicationContext * context = createAndRefreshContext(listeners, applicationArguments); **/
listeners.finished(context, null);
...
}private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}run方法中,加载了一系列SpringApplicationRunListener对象,在创建和更新ApplicationContext方法前后分别调用了listeners对象的started方法和finished方法, 并在创建和刷新ApplicationContext时,将listeners作为参数传递到了createAndRefreshContext方法中,以便在创建和刷新ApplicationContext的不同阶段,调用listeners的相应方法以执行操作。所以,所谓的SpringApplicationRunListeners实际上就是在SpringApplication对象的run方法执行的不同阶段,去执行一些操作,并且这些操作是可配置的。
同时,可以看到,加载SpringApplicationRunListener时,使用的是跟加载ApplicationContextInitializer和ApplicationListener时一样的方法。那么加载了什么,就可以从spring.factories文件中看到了:
以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
可以看到,在我们的例子中加载的是org.springframework.boot.context.event.EventPublishingRunListener。我们看一看这个SpringApplicationRunListener究竟做了点什么工作了?
以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListenerpublic EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.multicaster.addApplicationListener(listener);
}
}@Overridepublic void started() { publishEvent(new ApplicationStartedEvent(this.application, this.args));
}@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
environment));
}@Overridepublic void contextPrepared(ConfigurableApplicationContext context) { registerApplicationEventMulticaster(context);
}@Overridepublic void contextLoaded(ConfigurableApplicationContext context) { for (ApplicationListener<?> listener : this.application.getListeners()) { if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
} publishEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}@Overridepublic void finished(ConfigurableApplicationContext context, Throwable exception) { publishEvent(getFinishedEvent(context, exception));
}EventPublishingRunListener在对象初始化时,将SpringApplication对象的成员变量listeners全都保存下来,然后在自己的public方法被调用时,发布相应的事件,或执行相应的操作。可以说这个RunListener是在SpringApplication对象的run方法执行到不同的阶段时,发布相应的event给SpringApplication对象的成员变量listeners中记录的事件监听器。
下图画出了SpringApplicationRunListeners相关的类结构,虽然我们的例子中只有一个SpringApplicationRunListener,但在这样的设计下,想要扩展是非常容易的!
接下来,我们看一下在调用listeners的started方法。在我们的例子中,也就是发布了ApplicationStartedEvent时,我们已经加载的事件监听器都做了什么操作。至于其它事件的发布,我们按照代码执行的顺序在后面的章节在介绍。
ParentContextCloserApplicationListener不监听ApplicationStartedEvent,没有操作;
FileEncodingApplicationListener不监听ApplicationStartedEvent,没有操作;
AnsiOutputApplicationListener不监听ApplicationStartedEvent,没有操作;
ConfigFileApplicationListener不监听ApplicationStartedEvent,没有操作;
DelegatingApplicationListener不监听ApplicationStartedEvent,没有操作;
LiquibaseServiceLocatorApplicationListener监听ApplicationStartedEvent,会检查classpath中是否有liquibase.servicelocator.ServiceLocator并做相应操作;
以下代码摘自:org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener@Overridepublic void onApplicationEvent(ApplicationStartedEvent event) { if (ClassUtils.isPresent("liquibase.servicelocator.ServiceLocator", null)) { new LiquibasePresent().replaceServiceLocator();
}
}我们的例子中,classpath中不存在liquibase,所以不执行任何操作。
ClasspathLoggingApplicationListener监听ApplicationStartedEvent,会打印classpath到debug日志;
@Overridepublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartedEvent) { if (this.logger.isDebugEnabled()) { this.logger.debug("Application started with classpath: " + getClasspath());
}
...
}private String getClasspath() {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader instanceof URLClassLoader) { return Arrays.toString(((URLClassLoader) classLoader).getURLs());
} return "unknown";
}因为是debug级别的日志,而SpringBoot的默认日志级别是info级,所以我们在控制台不会看到classpath的输出。
LoggingApplicationListener监听ApplicationStartedEvent,会根据classpath中的类情况创建相应的日志系统对象,并执行一些初始化之前的操作;
@Overridepublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event);
}
...
}private void onApplicationStartedEvent(ApplicationStartedEvent event) { this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader()); this.loggingSystem.beforeInitialize();
}我们的例子中,创建的是org.springframework.boot.logging.logback.LogbackLoggingSystem类的对象,Logback是SpringBoot默认采用的日志系统。下图画出了SpringBoot中的日志系统体系:
好了,ApplicationStartedEvent事件的处理这样就结束了。以后在介绍事件处理的时候,我们只介绍监听该事件的监听器的操作,而不监听的,就不再说明了。
创建并刷新ApplicationContext
以下代码摘自:org.springframework.boot.SpringApplicationpublic 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对象,之后调用createAndRefreshContext方法创建并刷新一个ApplicationContext,最后调用afterRefresh方法在刷新之后做一些操作。
先来看看DefaultApplicationArguments吧:
以下代码摘自:org.springframework.boot.DefaultApplicationArgumentsDefaultApplicationArguments(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);
}
...
}
以下代码摘自:org.springframework.core.env.SimpleCommandLinePropertySourcepublic SimpleCommandLinePropertySource(String... args) { super(new SimpleCommandLineArgsParser().parse(args));
}可以看到是把main函数的args参数当做一个PropertySource来解析。我们的例子中,args的长度为0,所以这里创建的DefaultApplicationArguments也没有实际的内容。
创建并配置ApplicationConext的Environment
以下代码摘自:org.springframework.boot.SpringApplicationprivate 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;
}private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment;
} if (this.webEnvironment) { return new StandardServletEnvironment();
} return new StandardEnvironment();
}Spring Application的Environment代表着程序运行的环境,主要包含了两种信息,一种是profiles,用来描述哪些bean definitions是可用的;一种是properties,用来描述系统的配置,其来源可能是配置文件、JVM属性文件、操作系统环境变量等等。
首先要调用getOrCreateEnvironment方法获取一个Environment对象。在我们的例子中,执行到此处时,environment成员变量为null,而webEnvironment成员变量的值为true,所以会创建一个StandardServletEnvironment对象并返回。
之后是调用configureEnvironment方法来配置上一步获取的Environment对象,代码如下:
以下代码摘自:org.springframework.boot.SpringApplicationprivate Map<String, Object> defaultProperties;private boolean addCommandLineProperties = true;private Set<String> additionalProfiles = new HashSet<String>();protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) { configurePropertySources(environment, args); configureProfiles(environment, args);
}protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources(); if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast( new MapPropertySource("defaultProperties", this.defaultProperties));
} if (this.addCommandLineProperties && args.length > 0) {
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));
}
}
}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()]));
}configureEnvironment方法先是调用configurePropertySources来配置properties,然后调用configureProfiles来配置profiles。
configurePropertySources首先查看SpringApplication对象的成员变量defaultProperties,如果该变量非null且内容非空,则将其加入到Environment的PropertySource列表的最后。然后查看SpringApplication对象的成员变量addCommandLineProperties和main函数的参数args,如果设置了addCommandLineProperties=true,且args个数大于0,那么就构造一个由main函数的参数组成的PropertySource放到Environment的PropertySource列表的最前面(这就能保证,我们通过main函数的参数来做的配置是最优先的,可以覆盖其他配置)。在我们的例子中,由于没有配置defaultProperties且main函数的参数args个数为0,所以这个函数什么也不做。
configureProfiles首先会读取Properties中key为spring.profiles.active的配置项,配置到Environment,然后再将SpringApplication对象的成员变量additionalProfiles加入到Environment的active profiles配置中。在我们的例子中,配置文件里没有spring.profiles.active的配置项,而SpringApplication对象的成员变量additionalProfiles也是一个空的集合,所以这个函数没有配置任何active profile。
到现在,Environment就算是配置完成了。接下来调用SpringApplicationRunListeners类的对象listeners发布ApplicationEnvironmentPreparedEvent事件:
以下代码摘自:org.springframework.boot.context.event.EventPublishingRunListener@Overridepublic void environmentPrepared(ConfigurableEnvironment environment) { publishEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args,
environment));
}好,现在来看一看我们加载的ApplicationListener对象都有哪些响应了这个事件,做了什么操作:
FileEncodingApplicationListener响应该事件,检查file.encoding配置是否与spring.mandatory_file_encoding一致:
以下代码摘自:org.springframework.boot.context.FileEncodingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
event.getEnvironment(), "spring."); if (resolver.containsProperty("mandatoryFileEncoding")) {
String encoding = System.getProperty("file.encoding");
String desired = resolver.getProperty("mandatoryFileEncoding"); if (encoding != null && !desired.equalsIgnoreCase(encoding)) {
logger.error("System property 'file.encoding' is currently '" + encoding
+ "'. It should be '" + desired
+ "' (as defined in 'spring.mandatoryFileEncoding').");
logger.error("Environment variable LANG is '" + System.getenv("LANG")
+ "'. You could use a locale setting that matches encoding='"
+ desired + "'.");
logger.error("Environment variable LC_ALL is '" + System.getenv("LC_ALL")
+ "'. You could use a locale setting that matches encoding='"
+ desired + "'."); throw new IllegalStateException( "The Java Virtual Machine has not been configured to use the "
+ "desired default character encoding (" + desired
+ ").");
}
}
}在我们的例子中,因为没有spring.mandatory_file_encoding的配置,所以这个响应方法什么都不做。
AnsiOutputApplicationListener响应该事件,根据spring.output.ansi.enabled和spring.output.ansi.console-available对AnsiOutput类做相应配置:
以下代码摘自:org.springframework.boot.context.config.AnsiOutputApplicationListener@Overridepublic void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
event.getEnvironment(), "spring.output.ansi."); if (resolver.containsProperty("enabled")) {
String enabled = resolver.getProperty("enabled");
AnsiOutput.setEnabled(Enum.valueOf(Enabled.class, enabled.toUpperCase()));
} if (resolver.containsProperty("console-available")) {
AnsiOutput.setConsoleAvailable(
resolver.getProperty("console-available", Boolean.class));
}
}我们的例子中,这两项配置都是空的,所以这个响应方法什么都不做。
ConfigFileApplicationListener加载该事件,从一些约定的位置加载一些配置文件,而且这些位置是可配置的。
以下代码摘自:org.springframework.boot.context.config.ConfigFileApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
} if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event);
}
}private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
postProcessors.add(this);
AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(),
event.getSpringApplication());
}
}List<EnvironmentPostProcessor> loadPostProcessors() { return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}
以下内容摘自spring-boot-1.3.3.RELEASE.jar中的资源文件META-INF/spring.factories# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor可以看到,ConfigFileApplicationListener从META-INF/spring.factories文件中读取EnvironmentPostProcessor配置,加载相应的EnvironmentPostProcessor类的对象,并调用其postProcessEnvironment方法。在我们的例子中,会加载CloudFoundryVcapEnvironmentPostProcessor和SpringApplicationJsonEnvironmentPostProcessor并执行,由于我们的例子中没有CloudFoundry和Json的配置,所以这个响应,不会加载任何的配置文件到Environment中来。
DelegatingApplicationListener响应该事件,将配置文件中key为context.listener.classes的配置项,加载在成员变量multicaster中:
以下内容摘自:org.springframework.boot.context.config.DelegatingApplicationListenerprivate static final String PROPERTY_NAME = "context.listener.classes";private SimpleApplicationEventMulticaster multicaster;@Overridepublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) {
List<ApplicationListener<ApplicationEvent>> delegates = getListeners(
((ApplicationEnvironmentPreparedEvent) event).getEnvironment()); if (delegates.isEmpty()) { return;
} this.multicaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<ApplicationEvent> listener : delegates) { this.multicaster.addApplicationListener(listener);
}
} if (this.multicaster != null) { this.multicaster.multicastEvent(event);
}
}@SuppressWarnings("unchecked")private List<ApplicationListener<ApplicationEvent>> getListeners(
ConfigurableEnvironment env) {
String classNames = env.getProperty(PROPERTY_NAME);
List<ApplicationListener<ApplicationEvent>> listeners = new ArrayList<ApplicationListener<ApplicationEvent>>(); if (StringUtils.hasLength(classNames)) { for (String className : StringUtils.commaDelimitedListToSet(classNames)) { try {
Class<?> clazz = ClassUtils.forName(className,
ClassUtils.getDefaultClassLoader());
Assert.isAssignable(ApplicationListener.class, clazz, "class ["
+ className + "] must implement ApplicationListener");
listeners.add((ApplicationListener<ApplicationEvent>) BeanUtils
.instantiateClass(clazz));
} catch (Exception ex) { throw new ApplicationContextException( "Failed to load context listener class [" + className + "]",
ex);
}
}
}
AnnotationAwareOrderComparator.sort(listeners); return listeners;
}我们的例子中,因为没有key为context.listener.classes的Property,所以不会加载任何listener到该监听器中。
LoggingApplicationListener响应该事件,并对在ApplicationStarted时加载的LoggingSystem做一些初始化工作:
以下代码摘自:org.springframework.boot.logging.LoggingApplicationListener@Overridepublic void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationStartedEvent) { onApplicationStartedEvent((ApplicationStartedEvent) event);
} else if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
} else if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent((ApplicationPreparedEvent) event);
} else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) { onContextClosedEvent();
}
}private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) { if (this.loggingSystem == null) { this.loggingSystem = LoggingSystem
.get(event.getSpringApplication().getClassLoader());
} initialize(event.getEnvironment(), event.getSpringApplication().getClassLoader());
}protected void initialize(ConfigurableEnvironment environment,
ClassLoader classLoader) {
LogFile logFile = LogFile.get(environment); setSystemProperties(environment, logFile); initializeEarlyLoggingLevel(environment); initializeSystem(environment, this.loggingSystem, logFile); initializeFinalLoggingLevels(environment, this.loggingSystem); registerShutdownHookIfNecessary(environment, this.loggingSystem);
}在我们的例子中,是对加载的LogbackLoggingSystem做一些初始化工作。关于日志系统更详细的讨论,值得再写一篇文章,就不在这里展开讨论了。
打印banner
以下代码摘自:org.springframework.boot.SpringApplicationprivate 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);
}printBanner方法中,首先会调用selectBanner方法得到一个banner对象,然后判断bannerMode的类型,如果是Banner.Mode.LOG,那么将banner对象转换为字符串,打印一条info日志,否则的话,调用banner对象的printbanner方法,将banner打印到标准输出System.out。
在我们的例子中,bannerMode是Banner.Mode.Console,而且也不曾提供过banner.txt这样的资源文件。所以selectBanner方法中得到到便是默认的banner对象,即SpringBootBanner类的对象:
以下代码摘自:org.springframework.boot.SpringBootBannerprivate static final String[] BANNER = { "", " . ____ _ __ _ _", " /\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\", "( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\", " \\\\/ ___)| |_)| | | | | || (_| | ) ) ) )", " ' |____| .__|_| |_|_| |_\\__, | / / / /", " =========|_|==============|___/=/_/_/_/" };private static final String SPRING_BOOT = " :: Spring Boot :: ";private static final int STRAP_LINE_SIZE = 42;@Overridepublic 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();
}先打印个Spring的图形,然后打印个Spring Boot的文本,再然后打印一下Spring Boot的版本。会在控制台看到如下输出:
以下内容是程序启动后在console的输出: . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.3.3.RELEASE)
我的天。分析启动流程这么久,终于在屏幕有一行输出了,不容易。
创建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;
...
context = createApplicationContext();
context.setEnvironment(environment); 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(contextClass);
}createAndRefreshContext中调用createApplicationContext获取创建ApplicationContext,可以看到,当检测到本次程序是一个web应用程序(成员变量webEnvironment为true)的时候,就加载类DEFAULT_WEB_CONTEXT_CLASS,否则的话加载DEFAULT_CONTEXT_CLASS。我们的例子是一个web应用程序,所以会加载DEFAULT_WEB_CONTEXT_CLASS,也就是org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext。我们先来看一看这个AnnotationConfigEmbeddedWebApplicationContext具体有什么功能。下图画出了它的继承体系。
可以看到我们加载的这个AnnotationConfigEmbeddedWebApplicationContext类,从名字就可以看出来,首先是一个WebApplicationContext实现了WebApplicationContext接口,然后是一个EmbeddedWebApplicationContext,这意味着它会自动创建并初始化一个EmbeddedServletContainer,同时还支持AnnotationConfig,会将使用注解标注的bean注册到ApplicationContext中。更详细的过程,后面在例子中再一一剖析。
可以看到在加载类对象AnnotationConfigEmbeddedWebApplicationContext之后,createApplicationContext方法中紧接着调用BeanUtils的instantiate方法来创建ApplicationContext对象,其代码如下:
以下代码摘自:org.springframework.beans.BeanUtilspublic 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);
}
}通过调用Class对象的newInstance()方法来实例化对象,这等同于直接调用类的空的构造方法,所以我们来看AnnotationConfigEmbeddedWebApplicationContext类的构造方法:
以下代码摘自:org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContextpublic AnnotationConfigEmbeddedWebApplicationContext() { this.reader = new AnnotatedBeanDefinitionReader(this); this.scanner = new ClassPathBeanDefinitionScanner(this);
}@Overridepublic void setEnvironment(ConfigurableEnvironment environment) { super.setEnvironment(environment); this.reader.setEnvironment(environment); this.scanner.setEnvironment(environment);
}构造方法中初始化了两个成员变量,类型分别为AnnotatedBeanDefinitionReader和ClassPathBeanDefinitionScanner用以加载使用注解的bean定义。
这样ApplicationContext对象就创建出来了,在createAndRefreshContext方法中创建了ApplicationContext对象之后会紧接着调用其setEnvironment将我们之前准备好的Environment对象赋值进去。之后分别调用postProcessApplicationContext和applyInitializers做一些处理和初始化的操作。
先来看看postProcessApplicationContext:
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());
}
}
}如果成员变量beanNameGenerator不为Null,那么为ApplicationContext对象注册beanNameGenerator bean。如果成员变量resourceLoader不为null,则为ApplicationContext对象设置ResourceLoader。我们的例子中,这两个成员变量都为Null,所以什么都不做。
之后是applyInitializers方法:
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);
}Das obige ist der detaillierte Inhalt vonRelevante Einführung in den Startvorgang von Spring Boot. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!
Tutorial zur Sortier- und Suchliste der Listen nach angegebenen Spalten in JavaAug 26, 2025 pm 12:27 PMIn diesem Tutorial wird beschrieben, wie die Listenstrukturen in Java verarbeitet werden, um die Sortierung nach angegebenen "Spalten" zu erreichen und "Zeilen" effizient zu finden, die bestimmte Werte auf dieser Grundlage enthalten. Der Artikel vergleicht und sortiert Zeilendaten über den benutzerdefinierten Komparator und liefert Strategien zur Identifizierung von Zielspaltenindizes, wodurch häufige Herausforderungen beim Organisieren und Abrufen von Daten in komplexen verschachtelten Listen gelöst werden.
Spring WebFlux vs. Kotlin: Richtige Ausführung von CRUD -Operationen in reaktionsschnellen StreamsAug 26, 2025 pm 12:18 PMIn diesem Tutorial wird eingehend untersucht, wie CRUD-Operationen innerhalb von Mono- oder Flux-Abonnements bei der Entwicklung reaktionsschneller Anwendungen mithilfe von Spring WebFlux und Kotlin durchgeführt werden können, was dazu führen kann, dass Daten nicht bestehen bleiben. Der Kern besteht darin, die nicht blockierende Natur der reaktionsschnellen Programmierung zu verstehen und zu betonen, dass Nebeneffektoperationen in Abonnement-Rückrufen vermieden werden sollten. Durch den Vergleich von Fehlerbeispielen und korrekten Praktiken erläutert der Artikel im Detail, wie die Datenbankvorgänge nahtlos in Datenströme integriert werden, indem sie reaktionsschnelle Operatoren wie FlatMap unter Verwendung von reaktionsschnellen Prinzipien übereinstimmen.
Gradle Multi-Modul-Projektabhängigkeitskonfigurationshandbuch: Lösen von Problemen mit Subproject-AbhängigkeitAug 26, 2025 pm 12:15 PMDieser Artikel soll Entwicklern helfen, das Problem des Versagens in Unterprojektabhängigkeiten bei der Konstruktion von Gradle Multi-Modul-Projekt zu lösen. Mit detaillierten Code-Beispielen und Konfigurationsanweisungen werden wir in die ordnungsgemäße Konfiguration der Einstellungen.gradle-Datei und der Deklaration von Projektabhängigkeiten im Build.gradle-Datei des Subproject eintauchen, um sicherzustellen, dass Gradle Multi-Modul-Projekte korrekt anspürt und erstellt.
Implementierung von Java Caesar Passwort: So sorgen Sie ordnungsgemäß in TexträumenAug 26, 2025 pm 12:09 PMIn diesem Tutorial wird detailliert beschrieben, wie das Problem der fehlenden Räume in verschlüsselter Ausgabe bei der Implementierung von Caesar -Passwörtern in Java gelöst werden kann. Durch die Analyse gängiger Codierungsfehler, d. H. Falsches Überspringen von Speicherplätzen, enthält dieser Artikel ein modifiziertes Codebeispiel, mit dem sichergestellt wird, dass während des Verschlüsselungsprozesses die Leerzeichen erhalten bleiben und so erwartete verschlüsselte Text generiert werden. Das Tutorial erörtert ferner die Bedeutung der Anpassungsdefinitionen für die Charakter -Set mit Modulo -Operationen, damit Entwickler robustere Implementierungen von Caesar -Chiffren aufbauen können.
Lösungen für die Begrenzung dieser Referenz im Java -Konstruktor und zur kreisförmigen Abhängigkeit zwischen ObjektenAug 26, 2025 am 11:51 AMIn Java kann sich der Unterklassenkonstruktor darauf nicht verweisen, bevor er Super () anruft, da das Objekt zu diesem Zeitpunkt nicht vollständig initialisiert wurde, insbesondere die übergeordneten Klassenteil- und endgültigen Felder möglicherweise nicht zugewiesen. Wenn zyklische Abhängigkeiten zwischen den Objekten im Design auftreten, insbesondere im endgültigen Bereich, verursacht dies einen Kompilierungsfehler von "DIESES" DIESE ", bevor der Supertyp-Konstruktor aufgerufen wurde". Durch die Lösung dieses Problems muss das Design normalerweise angepasst werden, z. B. das Festlegen einer der kreisförmigen Abhängigkeiten in den Feldern ohne Finale und die Initialisierung der Aufruf von Super () oder die Verwendung flexiblerer Objekterstellung wie Builder-Muster.
Java Swing: Die richtige Art, Jradiobutton in String umzuwandelnAug 26, 2025 am 11:45 AMWenn Sie in Java Swing -Anwendungen den Benutzer aus der Jradiobutton -Gruppe ausgewählt werden müssen, erhalten Sie die Buttongroup.getSelection (). ToString () wird in der Regel ein nutzloses Objekthash -Wert erhalten. In diesem Artikel wird ausführlich erklärt, wie die ausgewählten Elemente des Jradiobutton korrekt in aussagekräftige Zeichenfolgen umwandeln können. Der Kern besteht darin, die SetActaceCommand () -Methode von Jradiobutton zu verwenden, um sie mit einem String -Befehl zu verknüpfen und diese angepasste Zeichenfolge über die GetActaTActionCommand () -Methode von ButtonModel zu erhalten, um eine genaue Datenerfassung zu erzielen.
Lösen Sie das Problem des JNA -Ladeausfalls während des Feder -Boot -Image -BuildsAug 26, 2025 am 11:39 AMDieser Artikel soll den Entwicklern helfen, den auf einem M1 -Mac aufgetretenen "Bibliotheksfehler zu laden können nicht geladen werden können, wenn ein Docker -Bild mit Spring Boot erstellt wird, das normalerweise mit dem fehlgeschlagenen Laden der JNA -Bibliothek (Java Native Access) zusammenhängt. Dieses Problem kann effektiv behoben werden, indem die Versionskompatibilität von Federschuh, Federwolke und Java angepasst wird, um ein reibungsloses Bildbau zu gewährleisten.
Vollständige Tutorial zum Erstellen einer Dockerfile mit TestContainernAug 26, 2025 am 11:36 AMIn diesem Dokument sollen Entwickler die Integration von Testkontainern während der Dockerfile -Builds leiten, um das Problem der Ausführung von Testfällen zu lösen, die von Docker -Umgebungen in CI oder lokalen Build -Umgebungen abhängen. Durch die Nutzung des Socat -Tools ist der Docker -Socket des Hosts der Build -Umgebung ausgesetzt, sodass Testcontainer während der Dockerfile -Build -Phase Container starten und verwalten können, wodurch vollständige Integrationstests durchgeführt werden.


Heiße KI -Werkzeuge

Undress AI Tool
Ausziehbilder kostenlos

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Clothoff.io
KI-Kleiderentferner

Video Face Swap
Tauschen Sie Gesichter in jedem Video mühelos mit unserem völlig kostenlosen KI-Gesichtstausch-Tool aus!

Heißer Artikel

Heiße Werkzeuge

Herunterladen der Mac-Version des Atom-Editors
Der beliebteste Open-Source-Editor

DVWA
Damn Vulnerable Web App (DVWA) ist eine PHP/MySQL-Webanwendung, die sehr anfällig ist. Seine Hauptziele bestehen darin, Sicherheitsexperten dabei zu helfen, ihre Fähigkeiten und Tools in einem rechtlichen Umfeld zu testen, Webentwicklern dabei zu helfen, den Prozess der Sicherung von Webanwendungen besser zu verstehen, und Lehrern/Schülern dabei zu helfen, in einer Unterrichtsumgebung Webanwendungen zu lehren/lernen Sicherheit. Das Ziel von DVWA besteht darin, einige der häufigsten Web-Schwachstellen über eine einfache und unkomplizierte Benutzeroberfläche mit unterschiedlichen Schwierigkeitsgraden zu üben. Bitte beachten Sie, dass diese Software

Dreamweaver Mac
Visuelle Webentwicklungstools

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

MinGW – Minimalistisches GNU für Windows
Dieses Projekt wird derzeit auf osdn.net/projects/mingw migriert. Sie können uns dort weiterhin folgen. MinGW: Eine native Windows-Portierung der GNU Compiler Collection (GCC), frei verteilbare Importbibliotheken und Header-Dateien zum Erstellen nativer Windows-Anwendungen, einschließlich Erweiterungen der MSVC-Laufzeit zur Unterstützung der C99-Funktionalität. Die gesamte MinGW-Software kann auf 64-Bit-Windows-Plattformen ausgeführt werden.







