Skip to content

Commit c1fec69

Browse files
Support constructor injection for FailureAnalyzers
This commit adds support for instantiating FailureAnalyzer implementations with BeanFactory and/or an Environment constructor arguments and deprecates support for setter injection of these values using BeanFactoryAware and EnvironmentAware. Closes gh-29811
1 parent 2d9177d commit c1fec69

File tree

13 files changed

+220
-140
lines changed

13 files changed

+220
-140
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzer.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
2020
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
2121
import org.springframework.boot.diagnostics.FailureAnalysis;
2222
import org.springframework.boot.jdbc.EmbeddedDatabaseConnection;
23-
import org.springframework.context.EnvironmentAware;
2423
import org.springframework.core.env.Environment;
2524
import org.springframework.util.ObjectUtils;
2625
import org.springframework.util.StringUtils;
@@ -33,13 +32,11 @@
3332
* @author Patryk Kostrzewa
3433
* @author Stephane Nicoll
3534
*/
36-
class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer<DataSourceBeanCreationException>
37-
implements EnvironmentAware {
35+
class DataSourceBeanCreationFailureAnalyzer extends AbstractFailureAnalyzer<DataSourceBeanCreationException> {
3836

39-
private Environment environment;
37+
private final Environment environment;
4038

41-
@Override
42-
public void setEnvironment(Environment environment) {
39+
DataSourceBeanCreationFailureAnalyzer(Environment environment) {
4340
this.environment = environment;
4441
}
4542

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzer.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,19 +18,21 @@
1818

1919
import org.jooq.DSLContext;
2020

21-
import org.springframework.beans.BeansException;
2221
import org.springframework.beans.factory.BeanFactory;
23-
import org.springframework.beans.factory.BeanFactoryAware;
2422
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
2523
import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
2624
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
2725
import org.springframework.boot.diagnostics.FailureAnalysis;
2826
import org.springframework.core.Ordered;
2927

3028
class NoDslContextBeanFailureAnalyzer extends AbstractFailureAnalyzer<NoSuchBeanDefinitionException>
31-
implements Ordered, BeanFactoryAware {
29+
implements Ordered {
3230

33-
private BeanFactory beanFactory;
31+
private final BeanFactory beanFactory;
32+
33+
NoDslContextBeanFailureAnalyzer(BeanFactory beanFactory) {
34+
this.beanFactory = beanFactory;
35+
}
3436

3537
@Override
3638
protected FailureAnalysis analyze(Throwable rootFailure, NoSuchBeanDefinitionException cause) {
@@ -60,10 +62,4 @@ public int getOrder() {
6062
return 0;
6163
}
6264

63-
@Override
64-
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
65-
this.beanFactory = beanFactory;
66-
67-
}
68-
6965
}

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzer.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
2020
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
2121
import org.springframework.boot.diagnostics.FailureAnalysis;
2222
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
23-
import org.springframework.context.EnvironmentAware;
2423
import org.springframework.core.env.Environment;
2524
import org.springframework.util.ObjectUtils;
2625
import org.springframework.util.StringUtils;
@@ -32,12 +31,11 @@
3231
* @author Mark Paluch
3332
*/
3433
class ConnectionFactoryBeanCreationFailureAnalyzer
35-
extends AbstractFailureAnalyzer<ConnectionFactoryBeanCreationException> implements EnvironmentAware {
34+
extends AbstractFailureAnalyzer<ConnectionFactoryBeanCreationException> {
3635

37-
private Environment environment;
36+
private final Environment environment;
3837

39-
@Override
40-
public void setEnvironment(Environment environment) {
38+
ConnectionFactoryBeanCreationFailureAnalyzer(Environment environment) {
4139
this.environment = environment;
4240
}
4341

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceBeanCreationFailureAnalyzerTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -60,8 +60,8 @@ void failureAnalysisIsPerformedWithActiveProfiles() {
6060
private FailureAnalysis performAnalysis(Class<?> configuration) {
6161
BeanCreationException failure = createFailure(configuration);
6262
assertThat(failure).isNotNull();
63-
DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer();
64-
failureAnalyzer.setEnvironment(this.environment);
63+
DataSourceBeanCreationFailureAnalyzer failureAnalyzer = new DataSourceBeanCreationFailureAnalyzer(
64+
this.environment);
6565
return failureAnalyzer.analyze(failure);
6666
}
6767

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jooq/NoDslContextBeanFailureAnalyzerTests.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -33,22 +33,22 @@
3333
*/
3434
class NoDslContextBeanFailureAnalyzerTests {
3535

36-
private final NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer();
37-
3836
@Test
3937
void noAnalysisWithoutR2dbcAutoConfiguration() {
4038
new ApplicationContextRunner().run((context) -> {
41-
this.failureAnalyzer.setBeanFactory(context.getBeanFactory());
42-
assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))).isNull();
39+
NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer(
40+
context.getBeanFactory());
41+
assertThat(failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class))).isNull();
4342
});
4443
}
4544

4645
@Test
4746
void analysisWithR2dbcAutoConfiguration() {
4847
new ApplicationContextRunner().withConfiguration(AutoConfigurations.of(R2dbcAutoConfiguration.class))
4948
.run((context) -> {
50-
this.failureAnalyzer.setBeanFactory(context.getBeanFactory());
51-
assertThat(this.failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class)))
49+
NoDslContextBeanFailureAnalyzer failureAnalyzer = new NoDslContextBeanFailureAnalyzer(
50+
context.getBeanFactory());
51+
assertThat(failureAnalyzer.analyze(new NoSuchBeanDefinitionException(DSLContext.class)))
5252
.isNotNull();
5353
});
5454
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/r2dbc/ConnectionFactoryBeanCreationFailureAnalyzerTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -58,8 +58,8 @@ void failureAnalysisIsPerformedWithActiveProfiles() {
5858
private FailureAnalysis performAnalysis(Class<?> configuration) {
5959
BeanCreationException failure = createFailure(configuration);
6060
assertThat(failure).isNotNull();
61-
ConnectionFactoryBeanCreationFailureAnalyzer failureAnalyzer = new ConnectionFactoryBeanCreationFailureAnalyzer();
62-
failureAnalyzer.setEnvironment(this.environment);
61+
ConnectionFactoryBeanCreationFailureAnalyzer failureAnalyzer = new ConnectionFactoryBeanCreationFailureAnalyzer(
62+
this.environment);
6363
return failureAnalyzer.analyze(failure);
6464
}
6565

Lines changed: 61 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2021 the original author or authors.
2+
* Copyright 2012-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,36 +16,36 @@
1616

1717
package org.springframework.boot.diagnostics;
1818

19-
import java.lang.reflect.Constructor;
20-
import java.util.ArrayList;
2119
import java.util.List;
20+
import java.util.stream.Collectors;
2221

2322
import org.apache.commons.logging.Log;
2423
import org.apache.commons.logging.LogFactory;
2524

2625
import org.springframework.beans.factory.BeanFactory;
2726
import org.springframework.beans.factory.BeanFactoryAware;
2827
import org.springframework.boot.SpringBootExceptionReporter;
28+
import org.springframework.boot.util.Instantiator;
29+
import org.springframework.boot.util.Instantiator.FailureHandler;
2930
import org.springframework.context.ConfigurableApplicationContext;
3031
import org.springframework.context.EnvironmentAware;
31-
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
32+
import org.springframework.core.env.Environment;
3233
import org.springframework.core.io.support.SpringFactoriesLoader;
3334
import org.springframework.core.log.LogMessage;
34-
import org.springframework.util.ClassUtils;
35-
import org.springframework.util.ReflectionUtils;
35+
import org.springframework.util.StringUtils;
3636

3737
/**
3838
* Utility to trigger {@link FailureAnalyzer} and {@link FailureAnalysisReporter}
3939
* instances loaded from {@code spring.factories}.
4040
* <p>
41-
* A {@code FailureAnalyzer} that requires access to the {@link BeanFactory} in order to
42-
* perform its analysis can implement {@code BeanFactoryAware} to have the
43-
* {@code BeanFactory} injected prior to {@link FailureAnalyzer#analyze(Throwable)} being
44-
* called.
41+
* A {@code FailureAnalyzer} that requires access to the {@link BeanFactory} or
42+
* {@link Environment} in order to perform its analysis can implement a constructor that
43+
* accepts arguments of one or both of these types.
4544
*
4645
* @author Andy Wilkinson
4746
* @author Phillip Webb
4847
* @author Stephane Nicoll
48+
* @author Scott Frederick
4949
*/
5050
final class FailureAnalyzers implements SpringBootExceptionReporter {
5151

@@ -56,54 +56,60 @@ final class FailureAnalyzers implements SpringBootExceptionReporter {
5656
private final List<FailureAnalyzer> analyzers;
5757

5858
FailureAnalyzers(ConfigurableApplicationContext context) {
59-
this(context, null);
59+
this(context, SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, getClassLoader(context)));
6060
}
6161

62-
FailureAnalyzers(ConfigurableApplicationContext context, ClassLoader classLoader) {
63-
this.classLoader = (classLoader != null) ? classLoader : getClassLoader(context);
64-
this.analyzers = loadFailureAnalyzers(context, this.classLoader);
62+
FailureAnalyzers(ConfigurableApplicationContext context, List<String> classNames) {
63+
this.classLoader = getClassLoader(context);
64+
this.analyzers = loadFailureAnalyzers(classNames, context);
6565
}
6666

67-
private ClassLoader getClassLoader(ConfigurableApplicationContext context) {
67+
private static ClassLoader getClassLoader(ConfigurableApplicationContext context) {
6868
return (context != null) ? context.getClassLoader() : null;
6969
}
7070

71-
private List<FailureAnalyzer> loadFailureAnalyzers(ConfigurableApplicationContext context,
72-
ClassLoader classLoader) {
73-
List<String> classNames = SpringFactoriesLoader.loadFactoryNames(FailureAnalyzer.class, classLoader);
74-
List<FailureAnalyzer> analyzers = new ArrayList<>();
75-
for (String className : classNames) {
76-
try {
77-
FailureAnalyzer analyzer = createAnalyzer(context, className);
78-
if (analyzer != null) {
79-
analyzers.add(analyzer);
80-
}
81-
}
82-
catch (Throwable ex) {
83-
logger.trace(LogMessage.format("Failed to load %s", className), ex);
84-
}
85-
}
86-
AnnotationAwareOrderComparator.sort(analyzers);
87-
return analyzers;
71+
private List<FailureAnalyzer> loadFailureAnalyzers(List<String> classNames,
72+
ConfigurableApplicationContext context) {
73+
Instantiator<FailureAnalyzer> instantiator = new Instantiator<>(FailureAnalyzer.class,
74+
(availableParameters) -> {
75+
if (context != null) {
76+
availableParameters.add(BeanFactory.class, context.getBeanFactory());
77+
availableParameters.add(Environment.class, context.getEnvironment());
78+
}
79+
}, new LoggingInstantiationFailureHandler());
80+
List<FailureAnalyzer> analyzers = instantiator.instantiate(this.classLoader, classNames);
81+
return handleAwareAnalyzers(analyzers, context);
8882
}
8983

90-
private FailureAnalyzer createAnalyzer(ConfigurableApplicationContext context, String className) throws Exception {
91-
Constructor<?> constructor = ClassUtils.forName(className, this.classLoader).getDeclaredConstructor();
92-
ReflectionUtils.makeAccessible(constructor);
93-
FailureAnalyzer analyzer = (FailureAnalyzer) constructor.newInstance();
94-
if (analyzer instanceof BeanFactoryAware || analyzer instanceof EnvironmentAware) {
84+
private List<FailureAnalyzer> handleAwareAnalyzers(List<FailureAnalyzer> analyzers,
85+
ConfigurableApplicationContext context) {
86+
List<FailureAnalyzer> awareAnalyzers = analyzers.stream()
87+
.filter((analyzer) -> analyzer instanceof BeanFactoryAware || analyzer instanceof EnvironmentAware)
88+
.collect(Collectors.toList());
89+
if (!awareAnalyzers.isEmpty()) {
90+
String awareAnalyzerNames = StringUtils.collectionToCommaDelimitedString(awareAnalyzers.stream()
91+
.map((analyzer) -> analyzer.getClass().getName()).collect(Collectors.toList()));
92+
logger.warn(LogMessage.format(
93+
"FailureAnalyzers [%s] implement BeanFactoryAware or EnvironmentAware."
94+
+ "Support for these interfaces on FailureAnalyzers is deprecated, "
95+
+ "and will be removed in a future release."
96+
+ "Instead provide a constructor that accepts BeanFactory or Environment parameters.",
97+
awareAnalyzerNames));
9598
if (context == null) {
96-
logger.trace(LogMessage.format("Skipping %s due to missing context", className));
97-
return null;
98-
}
99-
if (analyzer instanceof BeanFactoryAware) {
100-
((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
101-
}
102-
if (analyzer instanceof EnvironmentAware) {
103-
((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment());
99+
logger.trace(LogMessage.format("Skipping [%s] due to missing context", awareAnalyzerNames));
100+
return analyzers.stream().filter((analyzer) -> !awareAnalyzers.contains(analyzer))
101+
.collect(Collectors.toList());
104102
}
103+
awareAnalyzers.forEach((analyzer) -> {
104+
if (analyzer instanceof BeanFactoryAware) {
105+
((BeanFactoryAware) analyzer).setBeanFactory(context.getBeanFactory());
106+
}
107+
if (analyzer instanceof EnvironmentAware) {
108+
((EnvironmentAware) analyzer).setEnvironment(context.getEnvironment());
109+
}
110+
});
105111
}
106-
return analyzer;
112+
return analyzers;
107113
}
108114

109115
@Override
@@ -139,4 +145,13 @@ private boolean report(FailureAnalysis analysis, ClassLoader classLoader) {
139145
return true;
140146
}
141147

148+
static class LoggingInstantiationFailureHandler implements FailureHandler {
149+
150+
@Override
151+
public void handleFailure(Class<?> type, String implementationName, Throwable failure) {
152+
logger.trace(LogMessage.format("Skipping %s: %s", implementationName, failure.getMessage()));
153+
}
154+
155+
}
156+
142157
}

0 commit comments

Comments
 (0)