spring 注解笔记

spring 注解笔记

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、@ConditionalOnMissingBean

当给定的在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 要求制定的资源存在