Spring Boot(2) 注解解析

@AliasFor

@AliasFor注解用于声明注解属性的别名

显性注解内别名

表示注解中的两个属性相互互为别名

public @interface ContextConfiguration {
  
      @AliasFor("locations")
      String[] value() default {};
  
      @AliasFor("value")
      String[] locations() default {};
  
      // ...
}

实现要求:

  • 形成注解对的属性都必须使用@AliasFor标注,并且相互指向对方。从Spring Framework 5.2.1开始可以自定义注解对中的一个即可。不过,还是建议定义一对,以增强兼容性并让代码更好的被注释。
  • 属性对的返回类型必须相同
  • 属性对必须定义默认值,且必须相同.
  • 不能设置AliasFor注解的annotation属性

显性元注解属性别名

配置本注解的该属性就相当于配置了本注解的元注解的对应属性。简化了注解的属性配置。

@ContextConfiguration
public @interface XmlTestConfig {

  @AliasFor(annotation = ContextConfiguration.class, attribute = "locations")
  String[] xmlFiles();
   

实现要求:

  • @AliasFor必须指定annotation,且值对应的注解必须在本注解的元注解中
  • 匹配的属性对返回类型必须相同

@SpringBootApplication

用于注解Spring Boot应用的启动类,等效于同时使用@Configuration、@EnableAutoConfiguration和@ComponentScanIndicates三个注解:

  • @Configuration:允许被注解的类作为一个配置类,其内部有@Bean注解的方法将声明bean
  • @EnableAutoConfigurationa:启动自动注解扫描,将加载自动注解
  • @ComponentScan:配置扫描的范围.

例如

@SpringBootApplication
public class SpringbootApplication {

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

}

其主要代码如下

@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
// TypeExcludeFilter会自动找到所有注册到BeanFactory的TypeExcludeFilter子类,并用这些子类排除扫描范围
// AutoConfigurationExcludeFilter当前读取所有依赖包的META-INF/spring.factories文件,读取自动配置类。所有不在其中的自动配置类将排除出扫描范围。
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
     /**
	 * 设置要排除的自动配置类
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	Class<?>[] exclude() default {};

	/**
	 * 设置要排除的自动配置类名称
	 */
	@AliasFor(annotation = EnableAutoConfiguration.class)
	String[] excludeName() default {};

	/**
	 * 设置要扫描的基础包
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
	String[] scanBasePackages() default {};

	/**
	 * 设置要扫描的类
	 */
	@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
	Class<?>[] scanBasePackageClasses() default {};

	/**
	 * 设置被@Bean注解的方法,是否被包装,详见@Bean说明。
	 */
	@AliasFor(annotation = Configuration.class)
	boolean proxyBeanMethods() default true;

}

@Bean

通过代码定义Bean,和Spring XML定义Bean类似

@Bean
public MyBean myBean() {
   // instantiate and configure MyBean obj
   return obj;
}

Bean名称

可以通过@Bean注解的name属性定义Bean名称,如果未设置,则默认Bean名称是@Bean注解所在的方法名。

@Bean({"b1", "b2"})
public MyBean myBean() {
   return obj;
}

设置Bean生成的Profile条件

@Bean
@Profile("production")
public MyBean myBean() {
   return obj;
}

设置Bean的范围

@Bean
@Scope("prototype")
public MyBean myBean() {
   return obj;
}

@DependsOn

设置本Bean依赖的Bean,控制Bean生成的顺序

@Lazy

设置Bean在使用时才生成,只对默认的singleton访问有效

@Primary

设置如果有多个类型匹配注入类型时,优先使用的Bean。

被@Configuration注解的类中的@Bean方法

@Configuration
public class AppConfig {

   @Bean
   public FooService fooService() {
       return new FooService(fooRepository());
   }

   @Bean
   public FooRepository fooRepository() {
       return new JdbcFooRepository(dataSource());
   }

   // ...
}

一般@Bean注解的方法都在@Configuration注解的类中。这时,一个生成Bean的方法可以直接调用这个类中其他方法。这叫做Bean内引用,确保了Bean之间的引用是强类型并可导航,符合AOP语义(虽然是直接调用方法获取对象,但其实际获得的是包装的Bean对象,不是原始对象,因此AOP可以拦截此次调用)。其内部通过CGLIB的编织子类来实现,因此@Configuration注解的类必须不能是final或private的。

轻量级模式

在普通类或@Component注解的类中也可以通过@Bean注解的方法生成的Bean。这种@Bean方法被处理的过程叫做轻量级模式。

轻量级模式的Bean方法将被容器当做普通的工厂方法,其生成的对象不是被CGLIB修改的子类对象,而是原始Java对象。

轻量级模式下,当一个@Bean方法调用另一个@Bean方法时,这次调研是标准的Java方法调用,无法被CGLIB代理拦截。

@Component
public class Calculator {
   public int sum(int a, int b) {
       return a+b;
   }

   @Bean
   public MyBean myBean() {
       return new MyBean();
   }
}

@Configuration

指示内部有1或多个@Bean 方法的类将被Spring容器处理以生成Bean。

@Configuration
public class AppConfig {

   @Bean
   public MyBean myBean() {
       // instantiate, configure and return bean ...
   }
}

启动

通过AnnotationConfigApplicationContext加载
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
   ctx.register(AppConfig.class);
   ctx.refresh();
   MyBean myBean = ctx.getBean(MyBean.class);
   // use myBean ...
通过Spring XML加载

其中context:annotation-config/ 是必须的,用于启用 ConfigurationClassPostProcessor 等注解相关的PostProcessor(它们将帮助处理 @Configuration的类)

<beans>
  <context:annotation-config/>
  <bean class="com.acme.AppConfig"/>
</beans>
通过component scan

@Configuration注解有一个元注解是@Component,因此其注解的类可以使用component scan找到其他Bean,例如,以下通过构造函数自动注入其他Bean。

@Configuration
public class AppConfig {

   private final SomeBean someBean;

   public AppConfig(SomeBean someBean) {
       this.someBean = someBean;
   }

   // @Bean definition using "SomeBean"

}

也可以通过@ComponentScan 配置扫描范围

@Configuration
   @ComponentScan("com.acme.app.services")
   public class AppConfig {
       // various @Bean definitions ...
   }

和其他注解组合

@Import

如下例,只需要注册AppConfig,就可以让容器处理DatabaseConfig和AppConfig两个配置类。

@Configuration
public class DatabaseConfig {

   @Bean
   public DataSource dataSource() {
       // instantiate, configure and return DataSource
   }
}

@Configuration
@Import(DatabaseConfig.class)
public class AppConfig {

   private final DatabaseConfig dataConfig;

   public AppConfig(DatabaseConfig dataConfig) {
       this.dataConfig = dataConfig;
   }

   @Bean
   public MyBean myBean() {
       // reference the dataSource() bean method
       return new MyBean(dataConfig.dataSource());
   }
}
@ImportResource

和@Import类似,不过@ImportResource用于导入Spring XML配置文件

@Configuration
@ImportResource("classpath:/com/acme/database-config.xml")
public class AppConfig {

   @Inject DataSource dataSource; // from XML

   @Bean
   public MyBean myBean() {
       // inject the XML-defined dataSource bean
       return new MyBean(this.dataSource);
   }
}
@Configuration

@Configuration内部可以嵌套@Configuration

当AppConfig配置类加载时同时加载内部的DatabaseConfig

@Configuration
public class AppConfig {

   @Inject DataSource dataSource;

   @Bean
   public MyBean myBean() {
       return new MyBean(dataSource);
   }

   @Configuration
   static class DatabaseConfig {
       @Bean
       DataSource dataSource() {
           return new EmbeddedDatabaseBuilder().build();
       }
   }
}

@Profile

指定配置类生效的Profile

@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {

   @Bean
   public DataSource dataSource() {
       // instantiate, configure and return embedded DataSource
   }
}

@Profile("production")
@Configuration
public class ProductionDatabaseConfig {

   @Bean
   public DataSource dataSource() {
       // instantiate, configure and return production DataSource
   }
}

当然也可以在方法上和@Bean注解一起使用

 @Configuration
public class ProfileDatabaseConfig {

   @Bean("dataSource")
   @Profile("development")
   public DataSource embeddedDatabase() { ... }

   @Bean("dataSource")
   @Profile("production")
   public DataSource productionDatabase() { ... }
}

@Conditional及各类ConditionalOn

@Conditional

控制组件是否被注册到Spring容器,只有所有的Condition都满足才会注册

  • 用于注解类,该同时类直接或间接用@Component(包括@Configuration)注解,控制这个组件是否被注册
  • 注解到有@Bean注解的方法,控制该Bean是否注册
  • 作为注解的元注解,则该注解同样可以控制对象(如以上两条)是否注册

Conditional代码如下

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * 条件类数组,必须所有条件类的match方法都返回true才表示满足条件
	 */
	Class<? extends Condition>[] value();

}

Condition代码如下

@FunctionalInterface
public interface Condition {

	boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);

}

ConditionalOnXXX

Spring在包org.springframework.boot.autoconfigure.condition下提供了各种ConditionalOnXXX

以下以ConditionalOnJava注解类和OnJavaCondition条件类为例说明其实现

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 表示使用OnJavaCondition类进行判断
@Conditional(OnJavaCondition.class)
public @interface ConditionalOnJava {

	/**
	 * 定于区间,大于等于指定版本 或 低于指定版本
	 */
	Range range() default Range.EQUAL_OR_NEWER;

	/**
	 * 要比较的Java版本
	 */
	JavaVersion value();

	/**
	 * 区间选项
	 */
	enum Range {

		/**
		 * 大于等于指定版本
		 */
		EQUAL_OR_NEWER,

		/**
		 * 低于指定版本
		 */
		OLDER_THAN

	}

}
@Order(Ordered.HIGHEST_PRECEDENCE + 20)
class OnJavaCondition extends SpringBootCondition {

    // 获取当前Java版本
	private static final JavaVersion JVM_VERSION = JavaVersion.getJavaVersion();

	@Override
	public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
		// 通过metadata获取@ConditionalOnJava注解的属性值
		Map<String, Object> attributes = metadata.getAnnotationAttributes(ConditionalOnJava.class.getName());
		Range range = (Range) attributes.get("range");
		JavaVersion version = (JavaVersion) attributes.get("value");
		return getMatchOutcome(range, JVM_VERSION, version);
	}

	protected ConditionOutcome getMatchOutcome(Range range, JavaVersion runningVersion, JavaVersion version) {
	    // 比较版本,并返回ConditionOutcome包装的结果(包含匹配结果和匹配说明信息)
		boolean match = isWithin(runningVersion, range, version);
		String expected = String.format((range != Range.EQUAL_OR_NEWER) ? "(older than %s)" : "(%s or newer)", version);
		ConditionMessage message = ConditionMessage.forCondition(ConditionalOnJava.class, expected)
				.foundExactly(runningVersion);
		return new ConditionOutcome(match, message);
	}

	/**
	 * Determines if the {@code runningVersion} is within the specified range of versions.
	 * @param runningVersion the current version.
	 * @param range the range
	 * @param version the bounds of the range
	 * @return if this version is within the specified range
	 */
	private boolean isWithin(JavaVersion runningVersion, Range range, JavaVersion version) {
		if (range == Range.EQUAL_OR_NEWER) {
			return runningVersion.isEqualOrNewerThan(version);
		}
		if (range == Range.OLDER_THAN) {
			return runningVersion.isOlderThan(version);
		}
		throw new IllegalStateException("Unknown range " + range);
	}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值