springboot 原理 -自动配置

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

Guess you like

Origin blog.csdn.net/u012700515/article/details/121184283