Condition条件判断功能,通过这个功能可以实现选择性的创建Bean操作。
问题1:
SpringBoot是如何知道创建RedisTemplate的?
新建模块
查看对应springboot的启动方法返回值,返回的就是IOC容器。
ctrl+alt+v获取返回值,改写为
package com.yy.springboot_condition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
//获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
}
}
此时运行会报错,原因就是没有导入redis坐标
No bean named 'redisTemplate' available
pom.xml文件添加坐标后,再启动就可正常打印内存地址(如果还是不行就将maven reload一次)。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
问题1:对于有导入的坐标,就会创建Bean。
问题2:
SpringBoot是如何知道导没导入坐标?
创建User.java
package com.yy.springboot_condition.domain;
public class User {
}
修改启动类
此时运行可正常打印User内存地址。
package com.yy.springboot_condition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
//获取Bean,redisTemplate
// Object redisTemplate = context.getBean("redisTemplate");
// System.out.println(redisTemplate);
Object user = context.getBean("user");
System.out.println(user);
}
}
先查看Conditional注解;
这个注解里面需要一个数组,而数组Class又必须是Condition或子类;
查看Condition有一个matches方法,返回一个boolean值,也就是说如果返回True,Spring才会创建对象。
现在尝试Jedis坐标导入后,再加载User的bean类,没导入,则不加载。
编写测试
创建ClassCondition.java实现Condition接口,matches方法中通过Class.forName 是否能够加载对应的字节码判定有没有坐标,然后返回true或false
package com.yy.springboot_condition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import redis.clients.jedis.Jedis;
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求:导入jedis坐标后创建Bean
//根据Class.forName 是否能够加载对应的字节码判定有没有坐标
Boolean flag=true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
// e.printStackTrace();
flag=false;
}
return flag;
}
}
创建UserConfig.java
用@Conditional指定,此Bean的判定方法。
package com.yy.springboot_condition.config;
import com.yy.springboot_condition.condition.ClassCondition;
import com.yy.springboot_condition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Userconfig {
@Bean
@Conditional(ClassCondition.class)
public User user(){
return new User();
}
}
最后通过修改pom.xml文件,来测试jedis有没有导入坐标,对于User Bean的创建。
问题2:Bean类通过@Conditional注解,指定的ClassCondition类中matches方法返回的结果来创建Bean。
目录结构
将Jedis改为动态字节码判定
自己定义一个注解ConditionOnClass
package com.yy.springboot_condition.condition;
import org.springframework.context.annotation.Conditional;
import java.lang.annotation.*;
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//将Conditional注解内容的三个注解也添加到此处,其中Target代表本注解可以写到类上或方法;Retention注解生效时机;Documented生成javadoc文档。
@Conditional(ClassCondition.class)
//将原UserConfig中的此注解移到此将
public @interface ConditionOnClass {
String[] value();
//定义一个value属性,用于接收注解时设置的值
}
UserConfig.java修改为
package com.yy.springboot_condition.config;
import com.yy.springboot_condition.condition.ClassCondition;
import com.yy.springboot_condition.condition.ConditionOnClass;
import com.yy.springboot_condition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
@ConditionOnClass("redis.clients.jedis.Jedis")
public User user(){
return new User();
}
}
再运行也可正常打印User内存,说明自己定义的注解运行正常。
修改ClassCondition 通过metadata动态获取参数
package com.yy.springboot_condition.condition;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
import redis.clients.jedis.Jedis;
import java.util.Map;
public class ClassCondition implements Condition {
/**
* @param context 上下文对象,可获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象,可获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// context可用于获取配置文件中属性值
// Environment environment = context.getEnvironment();
// environment.getProperty();
//1.需求:导入jedis坐标后创建Bean
//根据Class.forName 是否能够加载对应的字节码判定有没有坐标
// Boolean flag=true;
// try {
// Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
//
// } catch (ClassNotFoundException e) {
e.printStackTrace();
// flag=false;
// }
// return flag;
//需求:导入通过注解属性值value指定坐标创建Bean
//获取注解属性值
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
String[] value = (String[]) map.get("value");
Boolean flag = true;
try {
for (String className : value) {
Class<?> cls = Class.forName(className);
}
} catch (ClassNotFoundException e) {
// e.printStackTrace();
flag = false;
}
return flag;
}
}
添加pom.xml fastjson用于测试
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.4</version>
</dependency>
修改UserConfig 传com.alibaba.fastjson.JSON用于测试
package com.yy.springboot_condition.config;
import com.yy.springboot_condition.condition.ClassCondition;
import com.yy.springboot_condition.condition.ConditionOnClass;
import com.yy.springboot_condition.domain.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
@Bean
// @Conditional(ClassCondition.class)
@ConditionOnClass("com.alibaba.fastjson.JSON")
public User user(){
return new User();
}
}
springboot自带,ConditionOnClass:有才对于字节码才创建Bean;ConditionOnClass:IOC中有这个Bean才创建指定的Bean;ConditionOnProperty:当你的配置文件中配置了指定属性才创建Bean;ConditionOnMissingBean:当IOC中没有这个Bean时才创建指定的Bean
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration部分代码
其中类上的ConditionalOnClass:有对应 字节码才会被创建Bean;
ConditionalOnMissingBean:没有对应redisTemplate Bean才会被创建;
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionOnProperty演示
重写UserConfig.java
package com.yy.springboot_condition.config;
import com.yy.springboot_condition.condition.ClassCondition;
import com.yy.springboot_condition.condition.ConditionOnClass;
import com.yy.springboot_condition.domain.User;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
@Configuration
public class UserConfig {
// @Bean
// @Conditional(ClassCondition.class)
// @ConditionOnClass("com.alibaba.fastjson.JSON")
// public User user(){
// return new User();
// }
@Bean
@ConditionalOnProperty(name="zhang",havingValue = "yy")
//当配置文件中有属性zhang且值为yy时被创建些Bean
public User user(){
return new User();
}
}
直接测试会报错
在application.yml添加属性值后运行正常
zhang:
yy