springboot 注解笔记
1、@Configuration
用于定义配置类,可替换xml配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,这些方法将会被AnnotationConfigApplicationContext或AnnotationConfigWebApplicationContext类进行扫描,并用于构建bean定义,初始化Spring容器。
2、@Bean
一般都在@Configuration指定下,替换xml中<bean>标签
3、@Import
将指定的Bean加入到IOC容器之中进行管理,有3种方式导入
类型一 : 导入某个配置类
类型二 : 导入某个ImportSelector
接口实现类
类型三 : 导入某个ImportBeanDefinitionRegistrar
接口实现类
3.1 导入配置类最简单,如下
@Import(Student.class)
作用就是new Student() 调用无参构造函数,其他都没做。
@Import上面的使用方式属于静态的导入依赖,如果用动态导入呢,比如要导入很多类,你总不至于每个写class,100个呢?吃不消,所以动态解决比如都写在一个properties配置文件中,扫描到全部导入。
这个时候就要用ImportSelector
接口实现类
3.2 ImportSelector
接口实现类
就只需要实现selectImports方法就行
1 public interface ImportSelector { 2 String[] selectImports(AnnotationMetadata var1); 3 }
只需要注意返回的数组是全名getName()
1 public class MyImportSelector implements ImportSelector 2 { 3 @Override 4 public String[] selectImports(AnnotationMetadata annotationMetadata) 5 { 6 return new String[]{Student.class.getName(), Address.class.getName()}; 7 } 8 }
1 @Configuration 2 @Import(MyImportSelector.class) 3 public class MainConfig 4 { 5 6 }
@Import还是要导入自定的MyImportSelector
Spring Boot就是这个原理用selectImports来扫描指定的文件导入
1 public String[] selectImports(AnnotationMetadata annotationMetadata) 2 { 3 if (!this.isEnabled(annotationMetadata)) { 4 return NO_IMPORTS; 5 } else { 6 AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader); 7 AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata); 8 return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations()); 9 } 10 }
4、@Conditional
spring boot中最常用的注解,是用来判断条件的。
它的作用是按照一定的条件进行判断,满足条件给容器注册bean。
源码如下:
1 @Target({ElementType.TYPE, ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 public @interface Conditional { 5 Class<? extends Condition>[] value(); 6 }
Condition是个接口,很明显填入的条件class必须实现Condition
然后实现matches方法,根据返回值true和false来判断是否注入。true为注入。
举例:
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 @Conditional(WindowsEnvironment.class) 6 public Student student() 7 { 8 Student student = new Student(); 9 return student; 10 } 11 } 12 13 public class WindowsEnvironment implements Condition 14 { 15 @Override 16 public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) 17 { 18 Environment environment = conditionContext.getEnvironment(); 19 System.out.println("environment:" + environment.getProperty("os.name")); 20 return true; 21 } 22 }
如果WindowsEnvironment的matches返回false,则不注册。
4.1、@ConditionalOnClass
继承于@Conditional
当给定的类名在类路径上存在,则实例化当前Bean
可以避免因为Class Not Found导致的编译异常了
源码:
1 @Target({ElementType.TYPE, ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Conditional({OnClassCondition.class}) 5 public @interface ConditionalOnClass { 6 Class<?>[] value() default {}; 7 8 String[] name() default {}; 9 }
既可以在Class上也可以在Method上。
1 public class Curose 2 { 3 private String name; 4 5 public String getName() 6 { 7 return name; 8 } 9 10 public void setName(String name) 11 { 12 this.name = name; 13 } 14 } 15 16 @Configuration 17 @ConditionalOnClass(value = Curose.class) 18 public class MyConfig 19 { 20 @Bean 21 public Person person() 22 { 23 return new Person(); 24 } 25 26 @Bean 27 public Student student() 28 { 29 Student student = new Student(); 30 return student; 31 } 32 }
因为存在Curose类,所以@ConditionalOnClass 存在class则加载MyConfig配置。
其中@ConditionalOnClass 可以写name,这个会更加便捷动态加载Class,其中name要写包名+类名。
@ConditionalOnClass(name = "service.Curose")
4.2、@ConditionalOnMissingClass
当给定的类名在类路径上不存在,则实例化当前Bean
4.3、@ConditionalOnBean
当给定的在bean存在时,则实例化当前Bean
源码如下:
1 @Target({ElementType.TYPE, ElementType.METHOD}) 2 @Retention(RetentionPolicy.RUNTIME) 3 @Documented 4 @Conditional({OnBeanCondition.class}) 5 public @interface ConditionalOnBean { 6 Class<?>[] value() default {}; 7 8 String[] type() default {}; 9 10 Class<? extends Annotation>[] annotation() default {}; 11 12 String[] name() default {}; 13 14 SearchStrategy search() default SearchStrategy.ALL; 15 16 Class<?>[] parameterizedContainer() default {}; 17 }
从上面的源代码中可以看出继承于@Conditional,判断是否加载bean逻辑都在OnBeanCondition.class中
可以用于Type和Method
例子:
1、用于Type中既class上
为了测试方便,我写了2个Configuration类MyConfig和MyConfig1
1 @Configuration 2 @ConditionalOnBean({Curose.class}) 3 public class MyConfig 4 { 5 @Bean 6 public Student student() 7 { 8 Student student = new Student(); 9 return student; 10 } 11 } 12 13 @Configuration 14 public class MyConfig1 15 { 16 @Bean 17 public Curose curose() 18 { 19 Curose curose = new Curose(); 20 return curose; 21 } 22 }
主程序加载配置类
1 public class ConditionalMain 2 { 3 public static void main(String[] args) 4 { 5 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig1.class, MyConfig.class); 6 String[] beanDefinitionNames = context.getBeanDefinitionNames(); 7 for (String name : beanDefinitionNames) 8 { 9 System.out.println(name); 10 } 11 } 12 }
AnnotationConfigApplicationContext 中加载的顺序注意下,我是先加载MyConfig1,再加载MyConfig,所以先会执行MyConfig1配置类再执行MyConfig
在MyConfig上加入注解@ConditionalOnBean({Curose.class}),说明了如果Curose Bean类在Ioc容器中是否存在,如果存在则加载这配置类,如果不存在,则不会加载配置类,既不会执行以下代码。这也是我为啥MyConfig1配置类写在前面的原因,首先加载了
MyConfig1配置,既将Curose类加入了容器中,ConditionalOnBean返回的是存在,既会加载MyConfig配置。
运行结果:
1 myConfig1 2 myConfig 3 curose 4 student
如果将上面的配置顺序换下,则不会加载MyConfig配置。
new AnnotationConfigApplicationContext(MyConfig.class, MyConfig1.class);
运行结果:
1 myConfig1 2 curose
如果换成@ConditionalOnMissingBean({Curose.class}) 则正好相反,不存在加载配置,存在则不加载配置
2、用于Method 方法上
于加载类上的区别在于,类上不启作用则全部不会不执行,写在方法上则表示这个方法不会执行而已。
例子:
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 public Person person() 6 { 7 return new Person(); 8 } 9 10 @Bean 11 @ConditionalOnBean({Curose.class}) 12 public Student student() 13 { 14 Student student = new Student(); 15 return student; 16 } 17 }
加入了一个Person类
执行结果:
1 myConfig1 2 myConfig 3 curose 4 person 5 student
全部都加载了
如果MyConfig1和MyConfig顺序相反下则student不会加载。
在方法上还有一个作用,如果是方法注入,如果没有这个注解必会抛出空指针异常,加入了则不会不执行就不会有异常,等待你加入即可
例子:
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 public Student student(Person person) 6 { 7 String name = person.getName(); 8 Student student = new Student(); 9 return student; 10 } 11 }
getName() 必然会出现空异常,因为Person类还没加载到Ioc容器中。
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 @ConditionalOnBean(Person.class) 6 public Student student(Person person) 7 { 8 String name = person.getName(); 9 Student student = new Student(); 10 return student; 11 } 12 }
加入了@ConditionalOnBean(Person.class) 后则Person不存在就不会执行了,等你哪天加载就执行,在Springboot 中很多应用就是这么干的,比如redis缓存。一开始创建项目就已经有RedisAutoConfiguration配置了,等你在pom.xml 加入spring-boot-starter-data-redis启动器后就会生效了。
生效代码如下:
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 public Person person() 6 { 7 return new Person(); 8 } 9 10 @Bean 11 @ConditionalOnBean(Person.class) 12 public Student student(Person person) 13 { 14 String name = person.getName(); 15 Student student = new Student(); 16 return student; 17 } 18 }
如果被Person在放Student下面就不会生效了,说明加载顺序是从上至下的。
1 @Configuration 2 public class MyConfig 3 { 4 @Bean 5 @ConditionalOnBean(Person.class) 6 public Student student(Person person) 7 { 8 String name = person.getName(); 9 Student student = new Student(); 10 return student; 11 } 12 13 @Bean 14 public Person person() 15 { 16 return new Person(); 17 } 18 }
以上student就不会加载到Ioc容器中。
4.4、@
当给定的在bean不存在时,则实例化当前Bean
4.5 、@ConditionalOnProperty
spring boot使用@ConditionalOnProperty注解来控制@Configuration是否生效
源代码:
1 @Retention(RetentionPolicy.RUNTIME) 2 @Target({ElementType.TYPE, ElementType.METHOD}) 3 @Documented 4 @Conditional({OnPropertyCondition.class}) 5 public @interface ConditionalOnProperty { 6 String[] value() default {}; //数组,获取对应property名称的值,与name不可同时使用 7 8 String prefix() default ""; //property名称的前缀 9 10 String[] name() default {}; //数组,property完整名称或部分名称(可与prefix组合使用,组成完整的property名称),与value不可同时使用 11 12 String havingValue() default ""; //可与name组合使用,比较获取到的属性值与havingValue给定的值是否相同,相同才加载配置 13 14 boolean matchIfMissing() default false; //缺少该property时是否可以加载。如果为true,没有该property也会正常加载;反之报错 15 }
例子:
创建了TestConfig.class,并设置了ConditionalOnProperty prefix = "context.test"
1 @Configuration 2 @ConditionalOnProperty(prefix = "context.test", name = "name", havingValue = "true") 3 public class TestConfig 4 { 5 @Bean 6 public UserContext userContext(){ 7 return new UserContext(); 8 } 9 }
直接执行会报错,因为找不到Property中的context.test.name
在yml配置中写了
context.test.name: true
就可以正常运行了。由name+havingValue进行绑定
如果将true写成false就会失败。
如果改写成value属性:
@ConditionalOnProperty(prefix = "context.test", value = "name")
也是返回true,加载配置。
如果将配置去除,加上matchIfMissing = true也是能运行成功加载配置的。
@ConditionalOnProperty(prefix = "context.test", value = "name", matchIfMissing = true)
注解 | 说明 |
---|---|
@ConditionalOnSingleCandidate |
当给定类型的bean存在并且指定为Primary的给定类型存在时,返回true |
@ConditionalOnMissingBean |
当给定的类型、类名、注解、昵称在beanFactory中不存在时返回true.各类型间是or的关系 |
@ConditionalOnBean |
与上面相反,要求bean存在 |
@ConditionalOnMissingClass |
当给定的类名在类路径上不存在时返回true,各类型间是and的关系 |
@ConditionalOnClass |
与上面相反,要求类存在 |
@ConditionalOnCloudPlatform |
当所配置的CloudPlatform为激活时返回true |
@ConditionalOnExpression |
spel表达式执行为true |
@ConditionalOnJava |
运行时的java版本号是否包含给定的版本号.如果包含,返回匹配,否则,返回不匹配 |
@ConditionalOnProperty |
要求配置属性匹配条件 |
@ConditionalOnJndi |
给定的jndi的Location 必须存在一个.否则,返回不匹配 |
@ConditionalOnNotWebApplication |
web环境不存在时 |
@ConditionalOnWebApplication |
web环境存在时 |
@ConditionalOnResource |
要求制定的资源存在 |