πŸ“œ ⬆️ ⬇️

Boot yourself, Spring is coming (Part 1)

Evgeny EvgenyBorisov Borisov (NAYA Technologies) and Kirill tolkkv Tolkachev (Cyan. Finance, Twitter ) talk about the most important and interesting points of Spring Boot using the example of a starter for an imaginary Iron Bank.



The article is based on a report by Eugene and Kirill from our Joker 2017 conference. Under the cut is a video and text transcript of the report.


The Joker conference is sponsored by many banks, so let's imagine that the application in which we study the work of the Spring boot and the starter that we create is associated with the bank.
')


So, suppose you have received an order for an application from the β€œIron Bank of Braavos”. An ordinary bank simply transfers money back and forth. For example, so (for this we have an API):

http://localhost:8080/credit\?name\=Targarian\&amount\=100

And in the "Iron Bank" before transferring money, it is necessary that the bank's API calculates whether a person can return them. Maybe he will not survive the winter and there will be no one to return. Therefore, there is a service that checks reliability.

For example, if we try to transfer money to Targaryen, the operation will be approved:



But if Stark, then no:



No wonder: Starks die too often. Why transfer money if a person does not survive the winter?

Let's see how it looks inside.

 @RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) {   long resultedDeposit = transferMoney.transfer(name, amount);   if (resultedDeposit == -1) {     return "Rejected<br/>" + name + " <b>will`t</b> survive this winter";   }   return format(       "<i>Credit approved for %s</i> <br/>Current  bank balance: <b>%s</b>",       name,       resultedDeposit   ); } @GetMapping("/state") public long currentState() {   return moneyDao.findAll().get(0).getTotalAmount(); } } 

This is a regular string controller.

Who is responsible for the logic of choice, to whom to issue a loan, and to whom - no? A simple line: if your name is Stark, just do not give out. In other cases - as lucky. An ordinary bank.

 @Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) {   return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } } 

Everything else is not so interesting. These are some annotations that do all the work for us. Everything is very fast.

Where are all the main configurations here? The controller is only one. In Dao, the interface is generally empty.

 public interface MoneyDao extends JpaRepository<Bank, String> { } 

In the services - only translation services and predictions to whom you can give. Directory Conf no. In fact, we only have application.yml (a list of those who return debts). And main is the most common:

 @SpringBootApplication @EnableConfigurationProperties(ProphetProperties.class) public class MoneyRavenApplication { public static void main(String[] args) {   SpringApplication.run(MoneyRavenApplication.class, args); } } 

So where is all the magic hidden?

The fact is that developers do not like to think about dependencies, configure configurations, especially if they are XML configurations, and think about how their application is launched. Therefore, Spring Boot solves these problems for us. We only need to write the application.

Dependencies


The first problem that we have always had is a version conflict. Every time we include different libraries that link to other libraries, dependency conflicts appear. Every time I read on the Internet that I need to add some entity-manager, a question arises, what version do I need to add so that it doesn't break anything?

Spring Boot solves the version conflict issue.

How do we usually get the Spring Boot project (if we haven't come to some place where it already exists)?


If we work with Maven, then the project will have pom.xml, where there is a parent Spring Boot, which is called spring-boot-dependencies . There will be a huge block of dependency-management.

I will not go into the details of Maven right now. Literally two words.

The dependency-management block does not prescribe dependencies. This is a block with which you can specify versions in case these dependencies are needed. And when you specify a dependency in the dependency-management block, without specifying the version, Maven starts looking for whether there is no dependency-management block in which the version is registered in parent pom-e or somewhere else. Those. in my project, adding a new dependency, I will no longer indicate the version in the hope that it is indicated somewhere in parent. And if she is not specified in parent, she definitely will not create any conflict with anyone. We have a good five hundred dependencies in dependency management, and they are all consistent with each other.

But what's the problem? The problem is that in my company, for example, there is a parent pom. If I want to use Spring, how can I be with my parent pom?



We do not have multiple inheritance. We want to use our pom, and receive the dependency-management block from the outside.



This can be done. It is enough to register the BOM import for the dependency-management block.

 <dependencyManagement> <dependencies>    <dependency>       <groupId>io.spring.platform</groupId>       <artifactId>platform-bom</artifactId>       <version>Brussels-SR2</version>       <type>pom</type>       <scope>import</scope>    </dependency> </dependencies> </dependencyManagement> 

Who wants to learn more about bom - see the report " Maven vs. Gradle ". There all this was explained in detail.

Today, among large companies, it has become quite fashionable to write such huge blocks of dependency-management, where they indicate all versions of their products and all versions of products that use their products and which do not conflict with each other. And this is called bom. This piece can be imported into your dependency-management block without inheritance.

And this is how it is done in Gradle (as usual, the same thing, only easier):

 dependencyManagement { imports {   mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } } 

Now let's talk about the dependencies themselves.

What do we prescribe in the application? Dependency management is good, but we want the application to have certain abilities, for example, that it responds via HTTP, to have a database or support for JPA. Therefore, all we need now is to get three dependencies.
It used to look like this. I want to work with the database and it starts: a transaction manager is needed, respectively, a spring-tx module is needed. I need some hibernate, so an EntityManager, hibernate-core or something else is required. I configure everything through Spring, then spring core is needed. That is, for one simple thing, you had to think about a dozen dependencies.

Today we have starters. The idea of ​​a starter is that we put a dependency on it. To begin with, he aggregates the dependencies that are needed for the world from which he came. For example, if this is a security starter, then you do not think about what dependencies are needed, they immediately arrive in the form of transitive dependencies to the starter. Or if you are working with Spring Data Jpa, then put a dependency on the starter, and it will bring all the modules you need to work with Spring Data Jpa.

Those. Our pom looks like this: it contains only those 3-5 dependencies that we need:

 'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2' 

With dependencies figured out, everything became easier. We now need to think less. There is no conflict, and the number of dependencies has decreased.

Context setting


Let's talk about the next pain that we have always had - setting the context. Every time we start writing an application from scratch, it takes a lot of time to set up the entire infrastructure. We wrote either in xml or in java config a lot of so-called infrastructure bins. If we worked with hibernate, we needed an EntityManagerFactory bin. Many infrastructure bins β€” transaction manager, data source, etc. - you had to tune with your hands. Naturally, they all fell into context.

During the report "The Spring-Ripper " we created a context in the main, and if it was an xml context, it was initially empty. If we built the context through AnnotationConfigApplicationContext , some beanpostprocessors that could configure the bins according to the annotations got there, but the context was also almost empty.
Now in the main, there is SpringApplication.run and no context is visible:

 @SpringBootApplilcation class App { public static void main(String[] args) {   SpringApplication.run(App.class,args); } } 

But in fact, we have a context. SpringApplication.run returns us some context.


This is a completely atypical case. There used to be two options:


In other words, the context was somehow. And we still passed some configuration classes to the input. By and large, we chose the type of context. Now we only have SpringApplication.run , it takes configurations as arguments and constructs the context

Riddle: what can we pass there?


Given:

RipperApplication.class
 public… main(String[] args) {  SpringApplication.run(?,args); } 

Question: what else can be transferred there?

Options:
  1. RipperApplication.class
  2. String.class
  3. "context.xml"
  4. new ClassPathResource("context.xml")
  5. Package.getPackage("conference.spring.boot.ripper")

Answer
Answer:
Documentation says that anything can be passed there. At a minimum, this will compile and will somehow work.



Those. in fact, all the answers are correct. Any one of them can be made to work, even String.class , and in some conditions you don’t even have to do anything to make it work. But this is a separate story.

The only thing that the documentation does not say is in what form we send there. But this is already from the field of secret knowledge.

 SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off 

SpringApplication is really important here - further along the slides we will have it by Carlson.

Our Carlson creates some kind of context based on the input data that we give him. I remind you that we are giving him, for example, such five wonderful options that can be made to work with the help of SpringApplication.run :


What does SpringApplication do for us?



When we created context in new through the new ourselves, we had a lot of different classes that implement the ApplicationContext interface:



And what options are there when the context is built by Carlson?

It makes only two kinds of context: either a web context ( WebApplicationContext ) or a generic context ( AnnotationConfigApplicationContext ).



Context selection is based on the presence of two classes in the classpath:



That is, the number of configurations has not decreased. To build context, we can specify all configuration options. To build context, I can pass a groovy script or xml; I can indicate which packages to scan or pass on a class marked with annotations. That is, I have all the possibilities.

However, it is Spring Boot. We have not created a single bean, have not written a single class, we only have main, and in it is our Carlson - SpringApplication.run . At the entrance, it gets a class labeled with some Spring Boot annotation.

If you look into this context, what will be there?

In our application, after connecting a pair of starters, there were 436 bins.



Nearly 500 bins just for what to start writing.



Next, we will understand where these bins come from.

But first of all we want to do the same.

The magic of the starters, besides the fact that they solved all the problems with dependencies to us, lies in the fact that we connected only 3-4 starters, and we have 436 bins. We would connect 10 starters, there would be strongly more than 1000 bins, because each starter, except for dependencies, already brings configurations that contain some necessary bins. Those. You said that you want a starter for the web, so you need a servlet dispatcher and an InternalResourceViewResolver . Connect the jpa starter - you need an EntityManagerFactory bin. All these bins are already somewhere in the configurations of starters are registered, and they magically come into the application without any action on our part.

To understand how this works, today we will write a starter, which will also bring to all applications that will use this starter, infrastructure bins.

Iron Law 1.1. Always send a crow




Let's look at the requirement from the customer. The Iron Bank has many different applications running in different branches. Customers want a raven to be sent every time an application is raised β€” information that the application has gone up.

Let's start writing code in the application of a specific Iron Bank (Iron bank). We will write a starter so that all Iron Bank applications that will depend on this starter can automatically send a crow. We remember that starters allow us to automatically tighten dependencies. And most importantly, we do not write almost any configuration.

We make a listener that listens to the context being updated (the last event), after which it sends the crow. We will listen to ContextRefreshEvent .

 public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } } 


Let's write the listener in the starter configuration. So far there will be only a listener, but tomorrow the customer will ask for some other infrastructure pieces, and we will also register them in this configuration.

 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


The question arises: how to make sure that the configuration of our starter is automatically pulled into all the applications that use this starter?

For all occasions there is β€œenable something.”



Really, if I depend on 20 starters, I have to put @Enable for everyone? And if the starter has several configurations? The main configuration class will be all hung up @Enable* , how is the Christmas tree?



Actually, I want to get some kind of inversion of control at the dependency level. I want to connect the starter (so that everything works), and do not know anything about what its insides are called. Therefore, we will use spring.factories.



So, what is spring.factories


The documentation says that there is such a spring.factories, in which you need to specify the correspondence of the interfaces and what you need to load on them - our configurations. And all this will magically appear in the context, and various conditions will be fulfilled on them.



Thus, we get the inversion of control, which we needed.



Let's try to implement. Instead of turning to the starter guts that I plugged in (take this configuration, and this one ...), everything will be exactly the opposite. The starter will have a file called spring.factories . In this file, we prescribe what configuration this starter should be activated for all who loaded it. A little later, I will explain exactly how this works in Spring Boot - at some point it starts scanning all the jars and looking for the spring.factories file.

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration 


 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


Now all we have to do is connect the starter in the project.

 compile project(':iron-starter') 


In maven it is similar - it is necessary to register dependency.

Run our application. The raven should fly up at the moment when it rises, although we did nothing in the application itself. In terms of infrastructure, we, of course, wrote and configured the starter. But from the point of view of the developer, we just connected the dependency and the configuration appeared - the crow flew. Everything we wanted.

This is not magic. Inversion control should not be magic. Also, how the use of Spring should not be magic. We know that this is a framework primarily for inversion of control. As there is an inversion of control for your code, so there is an inversion of control for modules.

@SpringBootApplication around the head


Remember the moment when we manually built the context in main. We wrote new AnnotationConfigApplicationContext and passed some configuration to the input, which was a java class. Now we also write SpringApplication.run and pass there a class that is a configuration, only it is marked with another rather powerful annotation @SpringBootApplication , which bears the whole world behind it.

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { … } 

First, there is @Configuration , that is, it is a configuration. There you can write @Bean and, as usual, prescribe bins.

Secondly, @ComponentScan stands above it. By default, it scans all packages and subpackages. Accordingly, if you start creating services in the same package or in its @Service β€” @Service , @RestController β€” they are automatically scanned, since the scanning process starts your main configuration.

In fact, @SpringBootApplication does nothing new. He simply collected all the best practices that were in applications on Spring, so that it is now some composition of annotations, including @ComponentScan .

In addition, there are still things that were not there before - @EnableAutoConfiguration . It was this class that I wrote in spring.factories.
@EnableAutoConfiguration , if you look, carries with it @Import :

 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  Class<?>[] exclude() default {};  String[] excludeName() default {}; } 

The main task of @EnableAutoConfiguration is to make an import that we wanted to get rid of in our application, because its implementation should have forced us to write the name of some class from the starter. And we can only learn it from the documentation. But everything must be on its own.

Need to pay attention to this class. It ends with ImportSelector . In the usual Spring, we write Import(Some Configuration.class) some configuration and it loads, like all dependent ones. Same ImportSelector , it is not a configuration. ImportSelector all our starters into context. It processes the @EnableAutoConfiguration annotation from spring.factories, which selects which configurations to load, and adds to the context those bins that we registered in IronConfiguration.



How he does it?

First of all, it uses the uncomplicated utility class, SpringFactoriesLoader, which looks at spring.factories and loads all of what is asked for. He has two methods, but they are not very different.



Spring Factories Loader existed in Spring 3.2, just nobody used it. It, apparently, was written as a potential development of the framework. And here it turned into Spring Boot, where there are a lot of mechanisms using the convention spring.factories. We will show further what else, besides the configuration, you can write in spring.factories - listeners, unusual processors, etc.

 static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl ) 

. open closed principle, - - . ( , ). , spring.factories. , . Spring Boot , , spring.factories.

. , Spring, , , , org.springframework.boot:spring-boot-autoconfigure , META-INF/spring.factories EnableAutoConfiguration , ( , , 80).

 spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ... 

, , Spring Boot, jar- (jar Spring Boot), spring.factories, 90 . , , CacheAutoConfiguration , β€” , :

 for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; 

, - , ( spring.factories). .

 private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC,    GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE,    EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST,  HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE,     JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE,  CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS,      RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE,   CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE,     SimpleCacheConfiguration.class); mappings.put(CacheType.NONE,       NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } 

, .



. But:



. β€” , , , open closed principle β€” spring.factories, . , - .

, Spring Boot, β€” 90 . 30 , Spring Boot.

, . 2013 , Spring 4, , @Conditional , conditions, , true false . , . java- Spring , conditional. , , conditional false , .

But there are nuances. , , , - .



Consider this by example.

1.2.


. β€” , . , , .



listener, , , . .

:

 @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 

, ? , : Β« Windows , , Windows, Β». conditional.

, : , : Β« Β». condition Spring Boot.

 @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { } 

- :

 public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {   return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } } 

Let's try.



, yes, (listener ).
, No, .

, @Conditional(OnProductionCondition.class) , , true false . , , - .


, @ConditionalOnProduction . , . , β€” @ConditionalOnProduction . , , 15 , . .

: , , , ?
, ? , , β€” .

:

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

: , . - β€” , .

. β€” , , .



, ( OnProductionCondition.class , β€” ). . , , , - . 5 ?

β€” 300 400. - . , , . β€” .

The situation is this. ( @Component , @Configuration @Service ), . , .

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } 

, .

 @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

, , . , - , , 300. , , . 400.

: ? :



, . : -, , , . , , .

1.3.


. - .



? , . ? data source, user .. , , , ? : application.properties application.yml . , IDEA.

? , , , β€” . .

β€” , , . listener-, . Those. , , . , , .

β€” , , , , .

. . - ?

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

, , . @ConditionalOnProperty . , , property property - , application.yml. @ConfigurationalProperty , .


, property . , , application.yml , .

property «». , .

 @ConfigurationProperties("") public class RavenProperties { List<String> ; } 

IDEA , - :



, ( Maven , Β« Β»). .

 subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } } 

IDEA, .

, . , annotation processor. , - . , Lombok annotation processor, β€” , .

property, application properties? JSON-, IDEA . property, IDEA . , property, , IDEA , :


@EnableConfigurationProperties , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } } 

, , , ( property, ).

:


. , β€” action .

, . .

 { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] } 


β€” - properties. properties . condition listener.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } } 


β€” , - - , .
, , application.yml.

 spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---:   -  : -: ,   : true 

, , .

. Spring constructor injection β€” . @Autowired , reflection. , Spring:

 @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

. , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

@Aurowired , Spring 4.3 . , @Aurowired . @RequiredArgsConstructor , . :

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

Spring Lombok. Jurgen Holler, 2002 80% Spring, @Aurowired , ( ).

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" … " + s); }); } } 

? RavenProperties Java-. @Aurowired , .

, . , , , .

1.4.




, . , . , , , .

. , . , . application properties, - , . , , data source, application properties. .. , , data source . ?

- , data source. ? ( ?)?

, - , , , . , .

, :

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

, @ConditionalOnMissingBean , . , , , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

, , , . .

, Event, listener β€” MyRavenListener .



.
β€” listener, - listener:

 @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } } 

β€” . Java-, .. , , .

extends - application listener, @ConditionalOnMissingBean . But since , ravenListener β€” . , Java- . , ravenListener .

? Spring Boot , . , , , . - , , . . , , , , , ( , , ). , , , , .

, β€” , . , . . , , . , - dataSource @Bean , dataSource @Bean .
, , , ID . - , , .

ConditionalOnPuzzler


@ConditionalOnClass , @ConditionalOnMissingBean , . .

, β€” . β€” . β€” .

 @Configuration public class  { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new (" "); } } 

?

: @ConditionalOnMissingClass ?

, , . , . . , . , , , ? ?

:

Answer
: , . reflection . exception, , β€” . reflection? , , , , β€” ClassDefNotFound .

ASM. , reflection β€” , Spring -, , . , , , @Conditional , . . ASM β€” , , . , , .

Juergen Hoeller , , , OnMissingClass , (String). , , ASM . , , .

1.5.




property β€” . . , .

. , / , ? . β€” , . , , - β€” .

@ConditionalOnProperty(".") .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

, : Duplicate annotation. , - , repeatable. property.

, String, β€” property.

 @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; } 

, . property , false - property, string . property .

:

 @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",  havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... } 

 @ConditionalOnProduction @ConditionalOnProperty( name = {    ".",    ".",    "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... } 

β€” .

, property, , : AllNestedConditions AnyNestedCondition .



, , . . β€” , . . .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

@Conditional() - .

 @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { } 


.

 public class OnRavenCondional implements Condition { } 

- , , :

 public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty(  name = ".",  havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty(  name = ".enabled",  havingValue = "true",  matchIfMissing = true) public static class OnRavenEnabled { } ... } 

Conditional , β€” AllNestedConditions , AnyNestedCondition β€” , . Those. @Condition :

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } } 

.

. - ( R).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} } 

( true ).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} } 

, . Spring Java-. IDEA, Java- , .

@ConditionalOnRaven . , @ConditionalOnProduction , @ConditionalOnMissingBean , . , .

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

. .
, β€” .

 @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; } 

Everything. false , true
application.yml:

 jpa.hibernate.ddl-auto: validate ironbank: ---: -  : : ,   : true 

, .

, repeatable. Java. .

, , .




. 19-20 Joker 2018, Β« [Joker Edition]Β» , Β«Micronaut vs Spring Boot, ?Β» . , Joker . .

!

Source: https://habr.com/ru/post/424503/


All Articles