一.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,现要求:
- 导入Jedis坐标后,加载该Bean,没导入,则不加载。
我们自定义一个配置类(UserConfig)和一个Condition的实现类(ClassCondition):
java
public class ClassCondition implements Condition {
@Override
public 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
}
用一个map存所有的标签名,然后通过遍历来一个个判断他们的坐标是否导入了,所有坐标都添加了才会创建对象。
java
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public 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 {
}
java
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
@Bean
public Student student() {
return new Student();
}
}
//3.ImportSelector 实现类:
java
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
//目前字符串数组的内容是写死的,未来可以设置在配置文件中动态加载
return new String[]{"com.apesource.domain.User", "com.apesource.domain.Student"};
}
//4.ImportBeanDefinitionRegistrar实现类:
java
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public 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);
//获取Bean
User 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);
//获取Bean
User 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);
//获取Bean
User 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);
//获取Bean
User user = context.getBean(User.class);
System.out.println(user);
}
}
结果: