一.Condition接口
Condition 是在Spring 4.0 增加的条件判断功能,通过这个可以功能可以实现选择性的创建 Bean 操 作。
当我们为spring容器添加了redis坐标后,我们就可以通过getBean()方法获取到redisTemplate对象,如果没有添加坐标则会报错,那么spring容器是怎么知道我们要配置那个类呢?
二.@conditional注解
其实spring容器是通过@conditional注解来判断我们是否添加了Redis坐标。
@conditional注解只有一个属性,就是一个Condition类型的数组,在Condition接口中只有一个matches方法,用于判断是否注入相关类,当matches返回值为true时,spring会进行注入,所以我们使用 @conditional注解时要给它一个属性值作为判断是否注入相关类的条件
所以我们用一个案例说明:
案例:
需求1: 在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
1. 导入Jedis坐标后,加载该Bean,没导入,则不加载。
我们自定义一个配置类(UserConfig)和一个Condition的实现类(ClassCondition):
java">public class ClassCondition implements Condition {@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {/**** @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象* @param metadata 注解元对象。 可以用于获取注解定义的属性值* @return*///1.需求: 导入Jedis坐标后创建Bean//思路:判断redis.clients.jedis.Jedis.class文件是否存在boolean flag = true;try {Class<?> cls = Class.forName("redis.clients.jedis.Jedis");} catch (ClassNotFoundException e) {flag = false;}return flag;}}
java">public class UserConfig {//@Conditional中的ClassCondition.class的matches方法,返回true执行以下代码,否则反之@Bean@Conditional(value= ClassCondition.class)public User user(){return new User();}}
@conditional注解的属性是我们自定义的Condition的实现类,在这个实现类中,我们重写了matches方法,用于自定义判断条件,当这个条件成立时,即我们导入了jedis坐标,spring会为我们自动注入相关类。
当我们没有注入jedis坐标时,matches返回值为false,所以就无法创建User对象:
java">@SpringBootApplication
public class Springboot815ConditionZidongpeizhiApplication {public static void main(String[] args) {//启动SpringBoot的应用,返回Spring的IOC容器ConfigurableApplicationContext context = SpringApplication.run(Springboot815ConditionZidongpeizhiApplication.class, args);/********************案例1********************/Object user = context.getBean("user");System.out.println(user);}
}
当我们注入jedis坐标后,我们成功获取到了User对象:
那当我们的判断条件为添加多个坐标时才创建对象,要是一个一个重写太麻烦,所以我们使用动态装配。
需求二:
在 Spring 的 IOC 容器中有一个 User 的 Bean,现要求:
将类的判断定义为动态的。判断哪个字节码文件存在可以动态指定
实现步骤:
不使用@Conditional(ClassCondition.class)注解 自定义注解@ConditionOnClass,因为他和之前@Conditional注解功能一直,所以直接复制 编写ClassCondition中的matches方法:
//1.自定义注解(ConditionOnClass):
java">import java.lang.annotation.*;
//自定义注解(仿照conditional注解)
@Target({ElementType.TYPE, ElementType.METHOD})//可以修饰在类与方法上
@Retention(RetentionPolicy.RUNTIME)//注解生效节点runtime
@Documented//生成文档
@Conditional(value=ClassCondition.class)public @interface ConditionOnClass {String[] value();//设置此注解的属性redis.clients.jedis.Jedis}
//2.配置类
用一个map存所有的标签名,然后通过遍历来一个个判断他们的坐标是否导入了,所有坐标都添加了才会创建对象。
java">public class ClassCondition implements Condition {/**** @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象* @param metadata 注解元对象。 可以用于获取注解定义的属性值* @return*/@Overridepublic boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());System.out.println(map);String[] value = (String[]) map.get("value");boolean flag = true;try {for (String className : value) {Class<?> cls = Class.forName(className);}} catch (ClassNotFoundException e) {flag = false;}return flag;}
}
此处的value为:"com.alibaba.fastjson.JSON","redis.clients.jedis.Jedis",只有这两个坐标均添加,才会创建user对象:
情况1:没有添加fastjson坐标
user对象无法创建:
当两个坐标都添加后:
三.@Enable注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启用某些功能的。而其底层原理 是使用@Import注 解导入一些配置类,实现Bean的动态加载
@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。 而@Import提供4中用法:
我们先创建一个import_demo工程,将他里面的对象导入另一个工程(demo):
//1.创建两个实体类(User和Student):
java">public class User {
}
java">public class Student {
}
//2.创建配置类:
java">@Configuration
public class UserConfig {@Beanpublic User user() {return new User();}@Beanpublic Student student() {return new Student();}}
//3.ImportSelector 实现类:
java">public class MyImportSelector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata importingClassMetadata) {//目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载return new String[]{"com.apesource.domain.User", "com.apesource.domain.Student"};}
//4.ImportBeanDefinitionRegistrar实现类:
java">public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//AnnotationMetadata注解//BeanDefinitionRegistry向spring容器中注入//1.获取user的definition对象AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();//2.通过beanDefinition属性信息,向spring容器中注册id为user的对象registry.registerBeanDefinition("user", beanDefinition);}
}
demo工程:
//1.导入 import_demo坐标
① 导入Bean
java">@SpringBootApplication
//@ComponentScan("com.apesource.import_demo02.config")
@Import(User.class)//导入javaBean
public class SpringbootImportDemo01Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);/*** @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包* 当前引导类所在包com.apesource.springbootenable03* 注入user类所在包com.apesource.springbootenable_other04.config* 因此扫描不到,所以容器中没有user* 解决方案:* 1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包* 2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* 3.可以对Import注解进行封装。**/
//Student student = context.getBean(Student.class);System.out.println(student);//获取BeanUser user = context.getBean(User.class);System.out.println(user);}
}
② 导入配置类
java">@SpringBootApplication
@Import(UserConfig.class)
public class SpringbootImportDemo01Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);/*** @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包* 当前引导类所在包com.apesource.springbootenable03* 注入user类所在包com.apesource.springbootenable_other04.config* 因此扫描不到,所以容器中没有user* 解决方案:* 1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包* 2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* 3.可以对Import注解进行封装。**/
//Student student = context.getBean(Student.class);System.out.println(student);//获取BeanUser user = context.getBean(User.class);System.out.println(user);}
}
③ 导入 ImportSelector 实现类。
java">@SpringBootApplication
@Import(MyImportSelector.class)
public class SpringbootImportDemo01Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);/*** @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包* 当前引导类所在包com.apesource.springbootenable03* 注入user类所在包com.apesource.springbootenable_other04.config* 因此扫描不到,所以容器中没有user* 解决方案:* 1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包* 2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* 3.可以对Import注解进行封装。**/
//Student student = context.getBean(Student.class);System.out.println(student);//获取BeanUser user = context.getBean(User.class);System.out.println(user);}
}
④ 导入 ImportBeanDefinitionRegistrar 实现类。
java">@SpringBootApplication
@Import({MyImportBeanDefinitionRegistrar.class})
public class SpringbootImportDemo01Application {public static void main(String[] args) {ConfigurableApplicationContext context = SpringApplication.run(ImportDemo02Application.class, args);/*** @SpringBootApplication中有@ComponentScan注解, 扫描范围:当前引导类所在包及其子包* 当前引导类所在包com.apesource.springbootenable03* 注入user类所在包com.apesource.springbootenable_other04.config* 因此扫描不到,所以容器中没有user* 解决方案:* 1.使用@ComponentScan扫描com.apesource.springbootenable_other04.config包* 2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器* 3.可以对Import注解进行封装。**/
//Student student = context.getBean(Student.class);System.out.println(student);//获取BeanUser user = context.getBean(User.class);System.out.println(user);}
}
结果: