Je vais commencer par un exemple simple. Vous disposez d'une application de démarrage Spring qui exécute une classe CommandLineRunner
lors de l'initialisation.
// MyCommandLineRunner.Java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
@Autowired //IntelliJ Warning
private DataSource ds;
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.Java
@SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner();
}
}
Maintenant, comme ça, ça marche, tout va bien. Cependant, IntelliJ signale un avertissement où @Autowired
est localisé (j'ai marqué où dans le commentaire)
L'équipe Spring recommande: Utilisez toujours l'injection de dépendance basée sur le constructeur dans vos beans. Utilisez toujours des assertions pour les dépendances obligatoires.
Maintenant, si je suis ceci, j'ai une injection de dépendance basée sur le constructeur
@Autowired
public MyCommandLineRunner(DataSource ds) { ... }
Cela signifie également que je dois modifier Application.Java
également, car le constructeur a besoin d'un argument. Dans Application.Java
si j'essaie d'utiliser l'injection de setter, j'obtiendrai le même avertissement. Si je refactorise cela aussi, je vais me retrouver avec, à mon avis, du code désagréable.
// MyCommandLineRunner.Java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private DataSource ds;
@Autowired // Note that this line is practically useless now, since we're getting this value as a parameter from Application.Java anyway.
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
// Application.Java
@SpringBootApplication
public class Application {
private DataSource ds;
@Autowired
public Application(DataSource ds) { this.ds = ds; }
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
Le code ci-dessus donne le même résultat, mais ne signale aucun avertissement dans IntelliJ. Je suis confus, en quoi le deuxième code est-il meilleur que le premier? Suis-je en train de suivre une logique incorrecte? Doit-il être câblé différemment?
En bref, quelle est la bonne façon de procéder?
note DataSource
n'est qu'un pur exemple, cette question s'applique à tout ce qui est câblé automatiquement.
note 2 Juste pour dire que MyCommandLineRunner.Java
ne peut pas avoir un autre constructeur vide, car DataSource doit être câblé/initialisé automatiquement. Il signalera une erreur et ne sera pas compilé.
Il existe plusieurs façons de l'améliorer.
Vous pouvez supprimer @Autowired
depuis votre MyCommandLineRunner
lorsque vous laissez un @Bean
la méthode en construit une instance. Injectez le DataSource
directement dans la méthode comme argument.
Ou supprimez @Autowired
et supprimez le @Bean
et gifler un @Component
annotation sur votre MyCommandLineRunner
pour la détecter et supprimer la méthode d'usine.
Intégrez votre MyCommandLineRunner
à l'intérieur de votre @Bean
méthode comme lambda.
MyCommandLineRunner
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
Et la classe d'application.
@SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
@Component
@Component
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds) { this.ds = ds; }
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: " + ds.toString());
}
}
Et la classe d'application.
@SpringBootApplication
public class Application {
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
}
CommandLineRunner
@SpringBootApplication
public class Application {
private static final Logger logger = LoggerFactory.getLogger(Application.class)
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return (args) -> (logger.info("DataSource: {}", ds);
}
}
Tous ces éléments sont des moyens valables de construire vos instances. Lequel utiliser, utilisez celui avec lequel vous vous sentez à l'aise. Il y a plus d'options (toutes les variantes de celles mentionnées ici).
Pensez à rendre le champ ds
final, alors vous n'avez pas besoin de @Autowired
. En savoir plus sur l'injection de dépendance http://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html#using-boot -spring-beans-and-dependency-injection
Pour garder le code propre, avez-vous envisagé d'utiliser des annotations Lombok? @RequiredArgsConstructor(onConstructor = @__(@Autowired))
générerait le constructeur avec des annotations @Autowired. Voir plus ici https://projectlombok.org/features/Constructor.html
Votre code pourrait ressembler à ceci:
@Slf4j
@RequiredArgsConstructor
// MyCommandLineRunner.Java
public class MyCommandLineRunner implements CommandLineRunner {
//final fields are included in the constructor generated by Lombok
private final DataSource ds;
@Override
public void run(String... args) throws Exception {
log.info("DataSource: {} ", ds.toString());
}
}
// Application.Java
@SpringBootApplication
@RequiredArgsConstructor(onConstructor_={@Autowired}) // from JDK 8
// @RequiredArgsConstructor(onConstructor = @__(@Autowired)) // up to JDK 7
public class Application {
private final Datasource ds;
public static void main(String... args) {
SpringApplication.run(Application.class, args);
}
@Bean
public MyCommandLineRunner schedulerRunner() {
return new MyCommandLineRunner(ds);
}
}
Édition ultérieure
La solution sans Lombok s'appuie sur Spring pour injecter la dépendance lors de la création du bean
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
/**
* dependency ds is injected by Spring
*/
public MyCommandLineRunner schedulerRunner(DataSource ds) {
return new MyCommandLineRunner(ds);
}
}
// MyCommandLineRunner.Java
public class MyCommandLineRunner implements CommandLineRunner {
private final Log logger = LogFactory.getLog(getClass());
private final DataSource ds;
public MyCommandLineRunner(DataSource ds){
this.ds = ds;
}
@Override
public void run(String... args) throws Exception {
logger.info("DataSource: "+ ds.toString());
}
}