当前位置 : 主页 > 编程语言 > java >

SpringBoot——starter解析

来源:互联网 收集:自由互联 发布时间:2023-02-04
Conditional注解解析 含义:基于条件的注解 作用:根据是否满足某个特定条件来决定是否创建某个特定的bean 意义:是SpringBoot实现自动配置的关键基础能力 @Conditional表示仅当所有指定条

Conditional注解解析

  • 含义:基于条件的注解
  • 作用:根据是否满足某个特定条件来决定是否创建某个特定的bean
  • 意义:是SpringBoot实现自动配置的关键基础能力

@Conditional表示仅当所有指定条件都匹配时,组件才有资格注册 。 该@Conditional注释可以在以下任一方式使用:

  • 作为任何@Bean方法的方法级注释
  • 作为任何类的直接或间接注释的类型级别注释 @Component,包括@Configuration类
  • 作为元注释,目的是组成自定义构造型注释

该注解主要源码之一,通过match匹配,符合条件才装载到Spring容器

@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Conditional { /** * All {@link Condition Conditions} that must {@linkplain Condition#matches match} * in order for the component to be registered. */ Class<? extends Condition>[] value(); } @FunctionalInterface public interface Condition { /** * Determine if the condition matches. * @param context the condition context * @param metadata metadata of the {@link org.springframework.core.type.AnnotationMetadata class} * or {@link org.springframework.core.type.MethodMetadata method} being checked * @return {@code true} if the condition matches and the component can be registered, * or {@code false} to veto the annotated component's registration */ boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata); } class ProfileCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) { return true; } } return false; } return true; } }

作用:总而言之,只有@Conditional指定的条件成立,才给容器添加组件

@Conditional派生注解:@Conditional派生了很多注解,下面给个表格列举一下派生注解的用法

@Conditional派生注解 作用(都是判断是否符合指定的条件) @ConditionalOnJava 系统的java版本是否符合要求 @ConditionalOnBean 有指定的Bean类 @ConditionalOnMissingBean 没有指定的bean类 @ConditionalOnExpression 符合指定的SpEL表达式 @ConditionalOnClass 有指定的类 @ConditionalOnMissingClass 没有指定的类 @ConditionalOnSingleCandidate 容器只有一个指定的bean,或者这个bean是首选bean @ConditionalOnProperty 指定的property属性有指定的值 @ConditionalOnResource 路径下存在指定的资源 @ConditionalOnWebApplication 系统环境是web环境 @ConditionalOnNotWebApplication 系统环境不是web环境 @ConditionalOnjndi JNDI存在指定的项

自定义Conditional注解

  • 新建MyConditionAnnotation注解,引入Condition实现类
@Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Conditional(MyCondition.class) public @interface MyConditionAnnotation { String[] value() default {}; }
  • 新建MyCondition实现Condition,重写matches方法,符合条件返回true
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String[] properties = (String[])metadata .getAnnotationAttributes("com.yibo.source.code.condi.MyConditionAnnotation") .get("value"); for (String property : properties) { if(StringUtils.isEmpty(context.getEnvironment().getProperty(property))){ return false; } } return true; } }
  • 新建A类使用@MyConditionAnnotation注解
@Component @MyConditionAnnotation({"com.yibo.condition1","com.yibo.condition2"}) public class A { }
  • 在application.properties中添加属性
com.yibo.condition1=test1 com.yibo.condition2=test2
  • 在测试类中测试
@RunWith(SpringRunner.class) @SpringBootTest(classes = Application.class) public class ApplicationTest implements ApplicationContextAware { @Autowired private ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } @Test public void test2(){ System.out.println(applicationContext.getBean(A.class)); } }

动手搭建starter

starter简介

SpringBoot可以省略SpringFramework众多的繁琐配置,它的众多starter可以说是功不可没。

 

springboot非常的流行,就是因为starter的存在,starter是springboot的核心,可以理解成可插拔的插件,你想要什么插件配置什么插件就可以,比如我想要使用mybatis,那么配置starter-mybatis就可以。但是有人会说我用mybatis自己导入jar不行吗???实际上starter和jar区别在于,它能够自己实现配置,这样大大提高了开发效率,使得使用spring开发变得非常简单方便。

  • starter类似于SpringBoot的可插拔插件
  • 与jar包区别:starter能实现自动配置
  • 作用:大幅提升开发效率

常用starter

名称 描述 spring-boot-starter-thymeleaf 使MVC Web applications 支持Thymeleaf spring-boot-starter-mail 使用Java Mail、Spring email发送支持 spring-boot-starter-data-redis 通过Spring Data Redis 、Jedis client使用Redis键值存储数据库 spring-boot-starter-web 构建Web,包含RESTful风格框架SpringMVC和默认的嵌入式容器Tomcat spring-boot-starter-activemq 为JMS使用Apache ActiveMQ spring-boot-starter-data-elasticsearch 使用Elasticsearch、analytics engine、Spring Data Elasticsearch spring-boot-starter-aop 通过Spring AOP、AspectJ面向切面编程 spring-boot-starter-security 使用 Spring Security spring-boot-starter-data-jpa 通过 Hibernate 使用 Spring Data JPA spring-boot-starter Core starter,包括 自动配置支持、 logging and YAML spring-boot-starter-freemarker 使MVC Web applications 支持 FreeMarker spring-boot-starter-batch 使用Spring Batch spring-boot-starter-data-solr 通过 Spring Data Solr 使用 Apache Solr spring-boot-starter-data-mongodb 使用 MongoDB 文件存储数据库、Spring Data MongoDB

手写starter

新建一个starter

首先我们在IDEA中通过Spring Initializer新建一个weather-starter的SpringBoot项目,然后我们可以在其他SpringBoot项目中作为依赖引入.

  • 1、修改weather-starter的pom文件,命名合理一点
<!--修改weather-starter的pom文件,命名合理一点--> <groupId>com.yibo</groupId> <artifactId>weather-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!--pom文件要加入--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
  • 2、新建一个类作为属性源,读取配置
@ConfigurationProperties(prefix = "weather") public class WeatherSource { private String type; private String rate; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getRate() { return rate; } public void setRate(String rate) { this.rate = rate; } }
  • 3、新建一个AutoConfiguration自动配置类,用来让SpringBoot依据它自动配置
@Configuration @EnableConfigurationProperties(WeatherSource.class) //自动注入的条件 @ConditionalOnProperty(name="weather.enable",havingValue = "enable") public class WeatherAutoConfiguration { @Autowired private WeatherSource weatherSource; @Bean @ConditionalOnMissingBean(WeatherService.class) public WeatherService weatherService(){ return new WeatherService(weatherSource); } }
  • 4、写一个类用来对外提供服务
public class WeatherService { private WeatherSource weatherSource; public WeatherService(WeatherSource weatherSource) { this.weatherSource = weatherSource; } public String getType(){ return weatherSource.getType(); } public String getRate(){ return weatherSource.getRate(); } }
  • 5、利用SPI机制,在resources目录下新建一个META-INF目录,新建一个spring.factories文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.weather.WeatherAutoConfiguration
  • 6、一切完毕之后,需要将我们的starter打包发布,让别人使用,本地使用的话可以通过maven helper插件install到本地仓库,也可以直接使用命令行打包

使用Starter

  • 1、在pom文件中引入依赖
<dependency> <groupId>com.example</groupId> <artifactId>weather-spring-boot-starter</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency>
  • 2、在application.properties中编写配置,用来配置自动配置类
weather.type=rain weather.rate=serious weather.enable=enable
  • 3、可以直接注入自动配置类中提供的服务
@RestController @RequestMapping("/demo") public class DemoController { @Autowired private WeatherService weatherService; @GetMapping("/weather") public String test(){ return weatherService.getType() + ", " + weatherService.getRate(); } }

starter原理解析

spring-boot启动的时候会找到starter jar包中的resources/META-INF/spring.factories文件,根据spring.factories文件中的配置,找到需要自动配置的类

 

字段配置的原理就是这边,在之前的SpringBoot--配置类解析已经分析过了,它会将容器中配置的自动配置类加载进来,见下图, // 这就引入了我们自己的WeatherAutoConfigration

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered { protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata, AnnotationMetadata annotationMetadata) { if (!isEnabled(annotationMetadata)) { return EMPTY_ENTRY; } AnnotationAttributes attributes = getAttributes(annotationMetadata); List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes); configurations = removeDuplicates(configurations); Set<String> exclusions = getExclusions(annotationMetadata, attributes); checkExcludedClasses(configurations, exclusions); configurations.removeAll(exclusions); configurations = filter(configurations, autoConfigurationMetadata); fireAutoConfigurationImportEvents(configurations, exclusions); return new AutoConfigurationEntry(configurations, exclusions); } }

starter自动配置类导入

starter过滤

我们发现WeatherAutoConfiguration自动配置类上面使用的是@ConditionalOnProperty注解,点进去看它用来判断的类是OnPropertyCondition,而OnPropertyCondition实现了SpringBootCondition接口,那么我们先看SpringBootCondition中的matches方法

@Configuration @EnableConfigurationProperties({WeatherSource.class}) @ConditionalOnProperty( name = {"weather.enable"}, havingValue = "enable" ) public class WeatherAutoConfiguration {} @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.TYPE, ElementType.METHOD }) @Documented @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { @Order(Ordered.HIGHEST_PRECEDENCE + 40) class OnPropertyCondition extends SpringBootCondition {} public abstract class SpringBootCondition implements Condition { private final Log logger = LogFactory.getLog(getClass()); @Override public final boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { String classOrMethodName = getClassOrMethodName(metadata); try { // 这边调用子类实现的getMatchOutcome方法得到ConditionOutcome // 看了下ConditionOutcome,它只是封装了判断结果和分析,所以我们直接看OnPropertyCondition的getMatchOutCome方法 ConditionOutcome outcome = getMatchOutcome(context, metadata); logOutcome(classOrMethodName, outcome); recordEvaluation(context, classOrMethodName, outcome); return outcome.isMatch(); } catch (NoClassDefFoundError ex) { throw new IllegalStateException("Could not evaluate condition on " + classOrMethodName + " due to " + ex.getMessage() + " not " + "found. Make sure your own configuration does not rely on " + "that class. This can also happen if you are " + "@ComponentScanning a springframework package (e.g. if you " + "put a @ComponentScan in the default package by mistake)", ex); } catch (RuntimeException ex) { throw new IllegalStateException("Error processing condition on " + getName(metadata), ex); } } } public class ConditionOutcome { private final boolean match; private final ConditionMessage message; }

接下来我们看OnPropertyCondition的getMatchOutcome方法

@Override public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) { // 首先获取@ConditionalOnProperty注解上的属性,如下图 List<AnnotationAttributes> allAnnotationAttributes = annotationAttributesFromMultiValueMap( metadata.getAllAnnotationAttributes(ConditionalOnProperty.class.getName())); // 构造noMatch、match集合,noMatch表示那个注解的属性没有满足 List<ConditionMessage> noMatch = new ArrayList<>(); List<ConditionMessage> match = new ArrayList<>(); // 依次遍历这些注解 for (AnnotationAttributes annotationAttributes : allAnnotationAttributes) { // 对每个注解,遍历其属性判断符不符合,返回outcome结果,下面我们也是重点看这个方法 ConditionOutcome outcome = determineOutcome(annotationAttributes, context.getEnvironment()); (outcome.isMatch() ? match : noMatch).add(outcome.getConditionMessage()); } if (!noMatch.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.of(noMatch)); } return ConditionOutcome.match(ConditionMessage.of(match)); }

继续跟进determineOutcome(annotationAttributes, context.getEnvironment())方法

private ConditionOutcome determineOutcome(AnnotationAttributes annotationAttributes, PropertyResolver resolver) { // 获取Spec,里面就是获取注解的属性封装一下,见下图 Spec spec = new Spec(annotationAttributes); // 两个集合,一个存放缺失的属性,一个存放不匹配的属性 List<String> missingProperties = new ArrayList<>(); List<String> nonMatchingProperties = new ArrayList<>(); // 遍历这些属性,判断符不符合,放入集合中 spec.collectProperties(resolver, missingProperties, nonMatchingProperties); // 根据结果返回 if (!missingProperties.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec) .didNotFind("property", "properties").items(Style.QUOTE, missingProperties)); } if (!nonMatchingProperties.isEmpty()) { return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnProperty.class, spec) .found("different value in property", "different value in properties") .items(Style.QUOTE, nonMatchingProperties)); } return ConditionOutcome .match(ConditionMessage.forCondition(ConditionalOnProperty.class, spec).because("matched")); } Spec(AnnotationAttributes annotationAttributes) { String prefix = annotationAttributes.getString("prefix").trim(); if (StringUtils.hasText(prefix) && !prefix.endsWith(".")) { prefix = prefix + "."; } this.prefix = prefix; this.havingValue = annotationAttributes.getString("havingValue"); this.names = getNames(annotationAttributes); this.matchIfMissing = annotationAttributes.getBoolean("matchIfMissing"); }

private void collectProperties(PropertyResolver resolver, List<String> missing, List<String> nonMatching) { // 依次遍历属性 for (String name : this.names) { String key = this.prefix + name; // 从上图中可以看出,resovler其实就是环境 if (resolver.containsProperty(key)) { if (!isMatch(resolver.getProperty(key), this.havingValue)) { nonMatching.add(name); } } else { if (!this.matchIfMissing) { missing.add(name); } } } } private boolean isMatch(String value, String requiredValue) { if (StringUtils.hasLength(requiredValue)) { return requiredValue.equalsIgnoreCase(value); } return !"false".equalsIgnoreCase(value); }

starter自动配置类过滤

参考: https://www.cnblogs.com/mzq123/p/11874128.html

https://my.oschina.net/liwanghong/blog/3168503

上一篇:SpringBoot——mybatis-starter解析
下一篇:没有了
网友评论