preface
You have already learned the Spring Boot configuration class and the principle of autowiring.
In this section, learn how to write a Spring Boot Starter.
@ Conditional annotations
Spring Boot auassemble automatically loads certain configuration classes into the Context.
If there is a load, there must be a filtering method, because we cannot load all the autoconfiguration classes at once, but load them on demand.
So, before we start creating Starter, we need to look at the @conditional annotation that loads the configuration class.
Conditional annotations are often used
-
@ConditionalOnProperty
Determines whether to load the configuration class based on a property.
Example: @conditionalonProperty (prefix = “book”, name = “book_name”, havingValue = “lalala”)
The class will be loaded only if there is a book.book_name=lalala property in the environment context.
-
@ConditionalOnBean
This class is loaded only when a Bean exists.
The opposite is: @conditionalonmissingBean
-
@ConditionalOnClass
A Class is loaded when it exists.
The opposite: @ ConditionalOnMissingClass
-
@ConditionalOnExpression
This class is loaded based on the expression (SpEL).
Example of Conditional
The following is the Spring Data Redis autoconfiguration class, which you can imitate when you need to write your own Starter.
ConditionalOnClass(redisoperations.class) // When redisoperations.class exists, Before loading such @ EnableConfigurationProperties (RedisProperties. Class) @ Import ({LettuceConnectionConfiguration. Class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "RedisTemplate ") // Only load the Bean named redisTemplate if there is no one in the Context. public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @bean @conditionalonMissingBean // Same as public StringRedisTemplate StringRedisTemplate (RedisConnectionFactory) redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }}Copy the code
Conditional principle
All of the above Conditional notes are derived from a single note.
- Indicates that a component is eligible for registration only if all specified conditions match.
- The @conditional annotation can be used in any of the following ways:
- As a type-level annotation for any class annotated directly or indirectly with @Component, including the @Configuration class
- As a meta-annotation for writing custom stereotype annotations
- As a method level annotation for any @bean method
- If the @Configuration class is marked @Conditional, then all @Bean methods, @Import @bean annotations, and @ComponentScan annotations with @Conditional will be Conditional.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
Copy the code
This annotation accepts a subclass of Condition. Let’s take a look at the Condition interface:
A single condition that must match to register a component.
Conditions are checked immediately before registering the bean definition, and registration is free to be rejected according to any criteria that can be determined at the time.
@functionalInterface public interface Condition {/* Determine whether the Condition matches. */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); }Copy the code
This interface has only one method to determine whether the conditions for loading configuration classes are met.
In the previous section of “Learning Spring Boot in Depth” (12), there is a section:
Protected void processConfigurationClass (ConfigurationClass configClass) throws IOException {/ / determine whether accord with the if analytical conditions (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) { return; }... // Recursively you are able to perform the excession-on-field measurements on the superclass hierarchy and on the excession-on-field measurements. / / equal to say doProcessConfigurationClass logic is the core of the processing load Configuration recursion. SourceClass sourceClass = asSourceClass(configClass); do { sourceClass = doProcessConfigurationClass(configClass, sourceClass); } while (sourceClass ! = null); this.configurationClasses.put(configClass, configClass); }Copy the code
Here the if statement is where the macth method is called, and if the if condition is true, the parsing of the configuration class is skipped.
ConditionEvaluator
This class mainly encapsulates the MACTH method. Here are two core methods:
/* The @conditional annotation determines whether an item should be skipped. */ public boolean shouldSkip(AnnotatedTypeMetadata metadata) { return shouldSkip(metadata, null); } public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @ Nullable ConfigurationPhase phase) {/ / conditions determine the if (metadata = = null | |! metadata.isAnnotated(Conditional.class.getName())) { return false; } // Since null is set above, the if body is always entered. If (phase == null) {// If (metadata instanceof AnnotationMetadata &&) ConfigurationClassUtils. IsConfigurationCandidate (AnnotationMetadata metadata)) {/ / set the ConfigurationPhase type to parse the configuration class return shouldSkip(metadata, ConfigurationPhase.PARSE_CONFIGURATION); } / / otherwise set ConfigurationPhase type for registered Bean return shouldSkip (metadata, ConfigurationPhase. REGISTER_BEAN); } List<Condition> conditions = new ArrayList<>(); for (String[] conditionClasses : getConditionClasses(metadata)) { for (String conditionClass : conditionClasses) { Condition condition = getCondition(conditionClass, this.context.getClassLoader()); conditions.add(condition); }} / / sorting AnnotationAwareOrderComparator. Sort (the conditions); // Execute the match method for (Condition Condition: conditions) {ConfigurationPhase requiredPhase = null; if (condition instanceof ConfigurationCondition) { requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase(); } if ((requiredPhase == null || requiredPhase == phase) && ! condition.matches(this.context, metadata)) { return true; } } return false; }Copy the code
Custom Starter
After all that groundwork, it’s easy to define a Starter. Take the autoConfiguration of Redis as an example.
-
Write configuration classes and add conditions
Here we register two of our most common templates.
@configuration // When RedisOperations exist, This class @ConditionalonClass (redisoperations.class) // displays the specify to load RedisProperties into the context @ EnableConfigurationProperties (RedisProperties. Class) / / Import other configuration class @ Import ({LettuceConnectionConfiguration. Class, JedisConnectionConfiguration.class }) public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }}Copy the code
-
Add the configuration class in spring.factories
# Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration Copy the code
conclusion
This section covers another important aspect of Spring Boot auto-configuration: Conditional annotations and the Conditional interface.
In addition, also a simple study of custom Starter. In fact, it is very easy to write your own Starter after we have learned about Spring configuration classes, Spring Boot auto-assembly and Conditionals.