Administrator
发布于 2024-07-11 / 9 阅读
0
0

the create of Bean

the create of Bean

通过ImportBeanDefinitionRegistrar动态注入Bean

ImportBeanDefinitionRegistrar使用

Spring官方通过ImportBeanDefinitionRegistrar实现了@Component、@Service等注解的动态注入机制。

很多三方框架集成Spring的时候,都会通过该接口,实现扫描指定的类,然后注册到spring容器中。 比如Mybatis中的Mapper接口,springCloud中的FeignClient接口,都是通过该接口实现的自定义注册逻辑。

所有实现了该接口的类的都会被ConfigurationClassPostProcessor处理,ConfigurationClassPostProcessor实现了BeanFactoryPostProcessor接口,所以ImportBeanDefinitionRegistrar中动态注册的bean是优先于依赖其的bean初始化,也能被aop、validator等机制处理。

基本步骤:

实现ImportBeanDefinitionRegistrar接口;
通过registerBeanDefinitions实现具体的类初始化;
在@Configuration注解的配置类上使用@Import导入实现类;

使用示例一:DtpBeanDefinitionRegistrar

1.在application.yaml中,配置如下:

spring:
  dynamic:
    tp:
      enabled: true
      enabledCollect: true                    # 是否开启监控指标采集,默认false
      collectorTypes: logging      # 监控数据采集器类型(logging | micrometer | internal_logging),默认micrometer
      #      logPath: /home/logs                     # 监控日志数据路径,默认 ${user.home}/logs,采集类型非logging不用配置
      monitorInterval: 30
      executors: # 动态线程池配置,都有默认值,采用默认值的可以不配置该项,减少配置量
        - threadPoolName: logRecordExecutor
          threadPoolAliasName: 日志记录线程池             # 线程池别名
          executorType: common                     # 线程池类型common、eager:适用于io密集型
          corePoolSize: 3
          maximumPoolSize: 8
          queueCapacity: 200
          queueType: LinkedBlockingQueue   # 任务队列,查看源码QueueTypeEnum枚举类
          rejectedHandlerType: DiscardOldestPolicy    # 拒绝策略,查看RejectedTypeEnum枚举类
          keepAliveTime: 50
          allowCoreThreadTimeOut: false                  # 是否允许核心线程池超时
          threadNamePrefix: logRecord

        - threadPoolName: heartBeatExecutor
          threadPoolAliasName: 心跳线程池             # 线程池别名
          executorType: scheduled                     # 线程池类型common、eager:适用于io密集型
          corePoolSize: 1
          maximumPoolSize: 1
          queueCapacity: 10
          queueType: LinkedBlockingQueue   # 任务队列,查看源码QueueTypeEnum枚举类
          rejectedHandlerType: DiscardOldestPolicy    # 拒绝策略,查看RejectedTypeEnum枚举类
          keepAliveTime: 50
          allowCoreThreadTimeOut: false                  # 是否允许核心线程池超时
          threadNamePrefix: heartBeat

接下来,通过读取上面的配置信息,创建对应的bean,注入到IOC容器中

2.首先,用一个实体类,来绑定配置文件中的配置信息。可以看到,配置文件中executors是一个数组,所以java实体类中,使用List集合,来绑定。如下:

@ConfigurationProperties(prefix = "spring.dynamic.tp")
public class DtpProperties {

    /**
     * If enabled DynamicTp.
     */
    private boolean enabled = true;

    /**
     * If print banner.
     */
    private boolean enabledBanner = true;

    /**
     * Config file type.
     */
    private String configType = "yml";

    /**
     * If enabled metrics collect.
     */
    private boolean enabledCollect = false;

    /**
     * Metrics collector types, default is logging.
     */
    private List<String> collectorTypes = Lists.newArrayList(CollectorTypeEnum.MICROMETER.name());

    /**
     * Metrics log storage path, just for "logging" type.
     */
    private String logPath;

    /**
     * Monitor interval, time unit(s)
     */
    private int monitorInterval = 5;

    /**
     * ThreadPoolExecutor configs.
     */
    private List<DtpExecutorProps> executors;

3.创建一个类DtpBeanDefinitionRegistrar,实现ImportBeanDefinitionRegistrar, EnvironmentAware这2个接口

其中,实现EnvironmentAware接口,是为了能够注入Environment,后续能够从Environment中取到配置文件中配置信息

而实现ImportBeanDefinitionRegistrar接口,是为了,动态注入bean。


public class DtpBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        DtpProperties dtpProperties = new DtpProperties();
        // 这里,是利用environment,读取配置文件中配置信息,保存到dtpProperties对象中
        PropertiesBinder.bindDtpProperties(environment, dtpProperties);
        val executors = dtpProperties.getExecutors();
        if (CollectionUtils.isEmpty(executors)) {
            log.warn("DynamicTp registrar, no executors are configured.");
            return;
        }

        executors.forEach(e -> {
            Class<?> executorTypeClass = ExecutorType.getClass(e.getExecutorType());
            Map<String, Object> propertyValues = buildPropertyValues(e);
            Object[] args = buildConstructorArgs(executorTypeClass, e);
            // 这里,是利用class、构造参数、类的属性值,构造出BeanDefinition,然后将BeanDefinition注册到BeanDefinitionRegistry中,后续ioc容器,就会自动创建对应的bean了
            BeanUtil.registerIfAbsent(registry, e.getThreadPoolName(), executorTypeClass, propertyValues, args);
        });
    }

下面,看下各自对应的详细方法:

// org.dromara.dynamictp.core.spring.PropertiesBinder#bindDtpProperties


 public static void bindDtpProperties(Environment environment, DtpProperties dtpProperties) {
        Binder binder = Binder.get(environment);
        ResolvableType type = ResolvableType.forClass(DtpProperties.class);
        Bindable<?> target = Bindable.of(type).withExistingValue(dtpProperties);
        binder.bind(MAIN_PROPERTIES_PREFIX, target);
    }

上面的这个方法,就是利用Binder的特性,从environment读取配置文件中配置信息,保存到dtpProperties对象中


private Map<String, Object> buildPropertyValues(DtpExecutorProps props) {
        Map<String, Object> propertyValues = Maps.newHashMap();
        propertyValues.put(THREAD_POOL_NAME, props.getThreadPoolName());
        propertyValues.put(THREAD_POOL_ALIAS_NAME, props.getThreadPoolAliasName());
        propertyValues.put(ALLOW_CORE_THREAD_TIMEOUT, props.isAllowCoreThreadTimeOut());
        propertyValues.put(WAIT_FOR_TASKS_TO_COMPLETE_ON_SHUTDOWN, props.isWaitForTasksToCompleteOnShutdown());
        propertyValues.put(AWAIT_TERMINATION_SECONDS, props.getAwaitTerminationSeconds());
        propertyValues.put(PRE_START_ALL_CORE_THREADS, props.isPreStartAllCoreThreads());
        propertyValues.put(REJECT_HANDLER_TYPE, props.getRejectedHandlerType());
        propertyValues.put(REJECT_ENHANCED, props.isRejectEnhanced());
        propertyValues.put(RUN_TIMEOUT, props.getRunTimeout());
        propertyValues.put(QUEUE_TIMEOUT, props.getQueueTimeout());

        val notifyItems = mergeAllNotifyItems(props.getNotifyItems());
        propertyValues.put(NOTIFY_ITEMS, notifyItems);
        propertyValues.put(PLATFORM_IDS, props.getPlatformIds());
        propertyValues.put(NOTIFY_ENABLED, props.isNotifyEnabled());

        val taskWrappers = TaskWrappers.getInstance().getByNames(props.getTaskWrapperNames());
        propertyValues.put(TASK_WRAPPERS, taskWrappers);

        return propertyValues;
    }

上面的这个方法,其实就是创建给一个Map,往map中存储各种key/value,key就是DtpExecutor类的属性名称,value就是配置文件中配置的信息。其实,目的就是,给DtpExecutor类的各属性,赋相应的值。类似与get/set方法。


 private Object[] buildConstructorArgs(Class<?> clazz, DtpExecutorProps props) {
        BlockingQueue<Runnable> taskQueue;
        if (clazz.equals(EagerDtpExecutor.class)) {
            taskQueue = new TaskQueue(props.getQueueCapacity());
        } else {
            taskQueue = buildLbq(props.getQueueType(),
                    props.getQueueCapacity(),
                    props.isFair(),
                    props.getMaxFreeMemory());
        }

        return new Object[] {
                props.getCorePoolSize(),
                props.getMaximumPoolSize(),
                props.getKeepAliveTime(),
                props.getUnit(),
                taskQueue,
                new NamedThreadFactory(props.getThreadNamePrefix()),
                RejectHandlerGetter.buildRejectedHandler(props.getRejectedHandlerType())
        };
    }

上面的这个方法,其实就是创建一个数组,数组中存放的是,DtpExecutor类构造方法,所需要的参数。这里,需要注意的是,数组中元素顺序,必须和DtpExecutor类定义的构造方法所需的参数顺序,保持一致。


// org.dromara.dynamictp.common.util.BeanUtil

public static void registerIfAbsent(BeanDefinitionRegistry registry,
                                        String beanName,
                                        Class<?> clazz,
                                        Map<String, Object> propertyValues,
                                        Object... constructorArgs) {
        if (ifPresent(registry, beanName, clazz) || registry.containsBeanDefinition(beanName)) {
            log.info("DynamicTp registrar, bean already exists and will be overwritten by remote config, beanName: {}",
                    beanName);
            registry.removeBeanDefinition(beanName);
        }
        doRegister(registry, beanName, clazz, propertyValues, constructorArgs);
    }



public static boolean ifPresent(BeanDefinitionRegistry registry, String beanName, Class<?> clazz) {
        String[] beanNames = getBeanNames((ListableBeanFactory) registry, clazz);
        return ArrayUtils.contains(beanNames, beanName);
    }

    public static String[] getBeanNames(ListableBeanFactory beanFactory, Class<?> clazz) {
        return beanFactory.getBeanNamesForType(clazz, true, false);
    }
  

上面的registerIfAbsent方法,首先判断,容器中,是否已经存在指定名称的指定类型的示例了,如果存在则移除,然后重新注册,调用doRegister方法


public static void doRegister(BeanDefinitionRegistry registry,
                                  String beanName,
                                  Class<?> clazz,
                                  Map<String, Object> propertyValues,
                                  Object... constructorArgs) {
         // 创建一个builder,这个builder专门来创建 BeanDefinition,所以,这里利用builder,来事先准备好所需的参数。包括构造参数、类各属性的值
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        for (Object constructorArg : constructorArgs) {
            builder.addConstructorArgValue(constructorArg);
        }
        if (MapUtils.isNotEmpty(propertyValues)) {
            propertyValues.forEach(builder::addPropertyValue);
        }
        // 这里,利用builder.getBeanDefinition(),获取到BeanDefinition,然后将BeanDefinition注册到registry中。这样,后续ioc容器,就会按照BeanDefinition,自动创建对应的bean了
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

4.在@Configuration注解的配置类上使用@Import导入实现类--DtpBeanDefinitionRegistrar

@Import(value = {DtpBeanDefinitionRegistrar.class})
@Configuration
public class DtpDynamicConfiguration {
}

使用示例二:RedisDynamicBeanDefinitionRegistrar

其实这个,和上面的例子,差不多。这个例子的作用是:在一个java项目中,能够实现连接多个redis,并且动态切换。

1.application.yaml 配置文件中的配置信息如下:


redis:
  dynamic:
    endpoints:
      # xuyi
      - company-id: 07b0c05f-935d-43f6-9e64-3813919e8aa1
        host: 218.94.128.34
        port: 16379
        database: 3
        password: long123456@HhRedis
        timeout: 15000
        lettuce:
          pool:
            min-idle: 0
            max-idle: 8
            max-active: 8
            max-wait: -1
      # hxhb
      - company-id: 76d843a4-94ba-47dc-9334-e9fefbfdd1a9
        host: 218.94.128.34
        port: 16379
        database: 4
        password: long123456@HhRedis
        timeout: 15000
        lettuce:
          pool:
            min-idle: 0
            max-idle: 8
            max-active: 8
            max-wait: -1
      # mq
      - company-id: ca06fe31-b226-4358-a568-f40a655ef418
        host: 218.94.128.34
        port: 16379
        database: 5
        password: long123456@HhRedis
        timeout: 15000
        lettuce:
          pool:
            min-idle: 0
            max-idle: 8
            max-active: 8
            max-wait: -1

2.指定一个java类,绑定配置文件中的配置信息

@ConfigurationProperties(value = "redis.dynamic")
public class RedisDynamicProperties {

    private List<RedisEndpointProps> endpoints;

    public List<RedisEndpointProps> getEndpoints() {
        return endpoints;
    }

    public void setEndpoints(List<RedisEndpointProps> endpoints) {
        this.endpoints = endpoints;
    }
}



public class RedisEndpointProps {

    private String companyId;

    /**
     * Login username of the redis server.
     */
    private String username;

    private String host;

    private int port;

    private int database;

    private String password;

    private Duration timeout;

    private final RedisProperties.Lettuce lettuce = new RedisProperties.Lettuce();

    private boolean ssl;

    private String clientName;

// get/set.......

}

3.创建一个ImportBeanDefinitionRegistrar的实现类---RedisDynamicBeanDefinitionRegistrar

public class RedisDynamicBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar, EnvironmentAware {

    private static final boolean COMMONS_POOL2_AVAILABLE = ClassUtils.isPresent("org.apache.commons.pool2.ObjectPool",
            RedisDynamicBeanDefinitionRegistrar.class.getClassLoader());

    private Environment environment;

    private static Map<String, Object> registerBean = new ConcurrentHashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        RedisDynamicProperties redisDynamicProperties = new RedisDynamicProperties();
        bindProperties(redisDynamicProperties);
        List<RedisEndpointProps> endpoints = redisDynamicProperties.getEndpoints();

        for (RedisEndpointProps endpoint : endpoints) {
            String companyId = endpoint.getCompanyId();
            // 创建letture连接客户端的配置,包括一些超时时间、最大多少个连接数等
            LettuceClientConfiguration lettuceClientConfiguration = lettuceClientConfiguration(endpoint);
            // 创建单例配置,包括连接redis server的ip/port/password/database等
            RedisStandaloneConfiguration standaloneConfig = getStandaloneConfig(endpoint);
            // 这里,创建一个LettuceConnectionFactory,然后存储到registerBean这个Map中,目的是,防止重复创建
            Supplier<LettuceConnectionFactory> lettuceConnectionFactorySupplier = () -> {
                LettuceConnectionFactory factory = (LettuceConnectionFactory) registerBean.get("LettuceConnectionFactory" + companyId);
                if (factory != null) {
                    return factory;
                }
                factory = new LettuceConnectionFactory(standaloneConfig, lettuceClientConfiguration);
                registerBean.put("LettuceConnectionFactory" + companyId,factory);
                return factory;
            };
            // 这里,是利用BeanDefinitionBuilder的genericBeanDefinition方法,提供class和instance,得到BeanDefinitionBuilder
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class, lettuceConnectionFactorySupplier);
            // 利用BeanDefinitionBuilder,得到BeanDefinition,然后将该BeanDefinition注册到registry中
            registry.registerBeanDefinition("lettuceConnectionFactory" + companyId, builder.getRawBeanDefinition());

            //
            LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactorySupplier.get();
        // 上面是往registry中注册了LettuceConnectionFactory的BeanDefinition,
        //这里,是往registry中,注册RedisTemplate的BeanDefinition
            registerRedisTemplate(registry, companyId, lettuceConnectionFactory);
        }
 }
    

各方法如下:


private void registerRedisTemplate(BeanDefinitionRegistry registry, String companyId, LettuceConnectionFactory lettuceConnectionFactory) {
        Map<String, Object> propertyValues = buildPropertyValues(lettuceConnectionFactory);
        BeanDefinitionBuilder redisTemplateBeanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(RedisTemplate.class);
        redisTemplateBeanDefinitionBuilder.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);

        doRegister(registry, RedisConstant.REDIS_TEMPLATE_STR.concat(companyId), redisTemplateBeanDefinitionBuilder,propertyValues,new Object[]{});
    }
    
    
    public void doRegister(BeanDefinitionRegistry registry,
                                String beanName,
                                BeanDefinitionBuilder beanDefinitionBuilder,
                                Map<String, Object> propertyValues,
                                Object... constructorArgs) {
        for (Object constructorArg : constructorArgs) {
            beanDefinitionBuilder.addConstructorArgValue(constructorArg);
        }
        if (MapUtils.isNotEmpty(propertyValues)) {
            propertyValues.forEach(beanDefinitionBuilder::addPropertyValue);
        }
        registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
    }
    

    private RedisStandaloneConfiguration getStandaloneConfig(RedisEndpointProps endpoint) {
        RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
        config.setHostName(endpoint.getHost());
        config.setPort(endpoint.getPort());
        config.setUsername(endpoint.getUsername());
        config.setPassword(RedisPassword.of(endpoint.getPassword()));
        config.setDatabase(endpoint.getDatabase());
        return config;
    }


    private void bindProperties(RedisDynamicProperties redisDynamicProperties) {
        Binder binder = Binder.get(environment);
        ResolvableType type = ResolvableType.forClass(RedisDynamicProperties.class);
        Bindable<?> target = Bindable.of(type).withExistingValue(redisDynamicProperties);
        binder.bind("redis.dynamic", target);
    }

    private Map<String, Object> buildPropertyValues(LettuceConnectionFactory lettuceConnectionFactory) {
        Map<String, Object> propertyValues = Maps.newHashMap();
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        GenericFastJsonRedisSerializer genericFastJsonRedisSerializer = new GenericFastJsonRedisSerializer();

        propertyValues.put("connectionFactory", lettuceConnectionFactory);
        propertyValues.put("keySerializer", stringRedisSerializer);
        propertyValues.put("valueSerializer", genericFastJsonRedisSerializer);

        propertyValues.put("hashKeySerializer", stringRedisSerializer);
        propertyValues.put("hashValueSerializer", genericFastJsonRedisSerializer);
        return propertyValues;
    }



    public LettuceClientConfiguration lettuceClientConfiguration(RedisEndpointProps endpoint) {

        RedisProperties.Lettuce lettuce = endpoint.getLettuce();
        RedisProperties.Pool pool = lettuce.getPool();
        GenericObjectPoolConfig<?> config = new GenericObjectPoolConfig<>();
        config.setMaxTotal(pool.getMaxActive());
        config.setMaxIdle(pool.getMaxIdle());
        config.setMinIdle(pool.getMinIdle());
        if (pool.getTimeBetweenEvictionRuns() != null) {
            config.setTimeBetweenEvictionRuns(pool.getTimeBetweenEvictionRuns());
        }
        if (pool.getMaxWait() != null) {
            config.setMaxWait(pool.getMaxWait());
        }
        //
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder().poolConfig(config);

        ClientOptions clientOptions = createClientOptions();
        DefaultClientResources clientResources = DefaultClientResources.builder().build();
        builder.clientOptions(clientOptions);
        builder.clientResources(clientResources);

        if (endpoint.isSsl()) {
            builder.useSsl();
        }
        if (endpoint.getTimeout() != null) {
            builder.commandTimeout(endpoint.getTimeout());
        }
        if (lettuce != null) {
            if (lettuce.getShutdownTimeout() != null && !lettuce.getShutdownTimeout().isZero()) {
                builder.shutdownTimeout(lettuce.getShutdownTimeout());
            }
        }
        if (StringUtils.hasText(endpoint.getClientName())) {
            builder.clientName(endpoint.getClientName());
        }
        return builder.build();
    }

    private ClientOptions createClientOptions() {
        ClientOptions.Builder builder = ClientOptions.builder();
        ClientOptions clientOptions = builder.timeoutOptions(TimeoutOptions.enabled()).build();
        return clientOptions;
    }

4.在@Configuration注解的配置类上使用@Import导入实现类;

@EnableConfigurationProperties(value = {RedisDynamicProperties.class})
@Import(value = {RedisDynamicBeanDefinitionRegistrar.class})
@Configuration
public class RedisDynamicConfiguration {
}


这里,涉及到了一个,有意思的方法:

// org.springframework.beans.factory.support.BeanDefinitionBuilder#genericBeanDefinition


Create a new BeanDefinitionBuilder used to construct a GenericBeanDefinition.
Params:
beanClass – the Class of the bean that the definition is being created for
instanceSupplier – a callback for creating an instance of the bean


	public static <T> BeanDefinitionBuilder genericBeanDefinition(Class<T> beanClass, Supplier<T> instanceSupplier) {
		BeanDefinitionBuilder builder = new BeanDefinitionBuilder(new GenericBeanDefinition());
		builder.beanDefinition.setBeanClass(beanClass);
		builder.beanDefinition.setInstanceSupplier(instanceSupplier);
		return builder;
	}

一般来说,都是,我们提供class、构造参数、属性值,来创建一个BeanDefinition,然后spring再利用提供的BeanDefinition,自动创建对应的实例。

但是,这里是提供了一个Supplier,也即将实例的创建,交给了开发者,spring框架不再自动创建该实例。这样,就会更加的灵活。


上面,我们往容器中,注册了多个redisTemplate,下面看下,在代码中,如何使用呢?

@Component
public class RedisDynamicTemplateHolder implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    private Map<String, RedisTemplate> companyIdRedisTemplateMap = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public RedisTemplate getTemplateByCompanyId(String companyId) {
        if (companyIdRedisTemplateMap.containsKey(companyId)) {
            return companyIdRedisTemplateMap.get(companyId);
        } else {
            RedisTemplate redisTemplate = applicationContext.getBean(RedisConstant.REDIS_TEMPLATE_STR.concat(companyId), RedisTemplate.class);
            if (Objects.nonNull(redisTemplate)) {
                synchronized (RedisDynamicTemplateHolder.class) {
                    if (companyIdRedisTemplateMap.containsKey(companyId)) {
                        return companyIdRedisTemplateMap.get(companyId);
                    }
                    redisTemplate.setScriptExecutor(new DefaultScriptExecutor(redisTemplate));
                    companyIdRedisTemplateMap.put(companyId, redisTemplate);
                    return redisTemplate;
                }
            } else {
                throw new UnsupportedOperationException("该集团id,暂时未接入");
            }
        }
    }
}



@Component
public class RedisCacheManager {

    @Autowired
    @Qualifier("userRedisTemplate")
    private RedisTemplate userRedisTemplate;

    @Autowired
    private RedisDynamicTemplateHolder redisDynamicTemplateHolder;

    private Map<String, RedisCacheHelper> companyIdRedisCacheHelperMap = new ConcurrentHashMap<>();

    private RedisCacheHelper userRedisCacheHelper;

    @PostConstruct
    public void initHelper() {
        userRedisCacheHelper = new RedisCacheHelper(userRedisTemplate);
    }

    public RedisCacheHelper getUserRedisCacheHelper() {
        return userRedisCacheHelper;
    }

    public RedisCacheHelper getBizRedisCacheHelper(String companyId) {
        if (StringUtils.isEmpty(companyId)) {
            throw new UnsupportedOperationException("companyId不能为空,请检查程序");
        }
        RedisCacheHelper redisCacheHelper = companyIdRedisCacheHelperMap.get(companyId);
        if (Objects.nonNull(redisCacheHelper)) {
            return redisCacheHelper;
        } else {
            RedisTemplate templateByCompanyId = redisDynamicTemplateHolder.getTemplateByCompanyId(companyId);
            if (Objects.nonNull(templateByCompanyId)) {
                companyIdRedisCacheHelperMap.put(companyId, new RedisCacheHelper(templateByCompanyId));
                return companyIdRedisCacheHelperMap.get(companyId);
            } else {
                throw new UnsupportedOperationException("获取cacheHelper失败,该集团id,暂时未接入");
            }
        }
    }

}

FactoryBean

这个FactoryBean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

为什么会有FactoryBean?

一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean。至于为什么会有FactoryBean?原因有两个:

1、
在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。

2、
由于第三方库不能直接注册到spring容器,于是可以实现org.springframework.bean.factory.FactoryBean接口,然后给出自己对象的实例化代码即可。


public interface FactoryBean<T> {
	//从工厂中获取bean【这个方法是FactoryBean的核心】
	@Nullable
	T getObject() throws Exception;
	
	//Return the type of object that this FactoryBean creates, or null if not known in advance.
This allows one to check for specific types of beans without instantiating objects, 
for example on autowiring.

	Class<?> getObjectType();
	
	//Bean工厂创建的对象是否是单例模式
	default boolean isSingleton() {
		return true;
	}
}

使用示例一:FeignClientFactoryBean

// org.springframework.cloud.openfeign.FeignClientsRegistrar#registerFeignClient

private void registerFeignClient(BeanDefinitionRegistry registry, AnnotationMetadata annotationMetadata,
			Map<String, Object> attributes) {
		String className = annotationMetadata.getClassName();
		Class clazz = ClassUtils.resolveClassName(className, null);
		ConfigurableBeanFactory beanFactory = registry instanceof ConfigurableBeanFactory
				? (Configurab
                                leBeanFactory) registry : null;
		String contextId = getContextId(beanFactory, attributes);
		String name = getName(attributes);
                
     // 这里创建了一个FactoryBean,通过编码的方式,给FactoryBean提供很多属性值,后面利用FactoryBean的getObject方法,得到一个FeignClient
                
		FeignClientFactoryBean factoryBean = new FeignClientFactoryBean();
		factoryBean.setBeanFactory(beanFactory);
		factoryBean.setName(name);
		factoryBean.setContextId(contextId);
		factoryBean.setType(clazz);
		factoryBean.setRefreshableClient(isClientRefreshEnabled());
                
  // 这里得到一个BeanDefinitionBuilder,后续利用此BeanDefinitionBuilder,得到BeanDefinition,然后将BeanDefinition注册到registry中
		BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(clazz, () -> {
			factoryBean.setUrl(getUrl(beanFactory, attributes));
			factoryBean.setPath(getPath(beanFactory, attributes));
			factoryBean.setDecode404(Boolean.parseBoolean(String.valueOf(attributes.get("decode404"))));
			Object fallback = attributes.get("fallback");
			if (fallback != null) {
				factoryBean.setFallback(fallback instanceof Class ? (Class<?>) fallback
						: ClassUtils.resolveClassName(fallback.toString(), null));
			}
			Object fallbackFactory = attributes.get("fallbackFactory");
			if (fallbackFactory != null) {
				factoryBean.setFallbackFactory(fallbackFactory instanceof Class ? (Class<?>) fallbackFactory
						: ClassUtils.resolveClassName(fallbackFactory.toString(), null));
			}
			return factoryBean.getObject();
		});
		definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		definition.setLazyInit(true);
		validate(attributes);

		AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
		beanDefinition.setAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE, className);
		beanDefinition.setAttribute("feignClientsRegistrarFactoryBean", factoryBean);

		// has a default, won't be null
		boolean primary = (Boolean) attributes.get("primary");

		beanDefinition.setPrimary(primary);

		String[] qualifiers = getQualifiers(attributes);
		if (ObjectUtils.isEmpty(qualifiers)) {
			qualifiers = new String[] { contextId + "FeignClient" };
		}

		BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className, qualifiers);
		BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);

		registerOptionsBeanDefinition(registry, contextId);
	}

下面,我们来看下上面,是如何利用FeignClientFactoryBean的getObject方法,得到FeignClient的

// org.springframework.cloud.openfeign.FeignClientFactoryBean#getTarget

// 这个方法,最后返回一个FeignClient实例

<T> T getTarget() {
// 这里的beanFactory就是ConfigurableBeanFactory
		FeignContext context = beanFactory != null ? beanFactory.getBean(FeignContext.class)
				: applicationContext.getBean(FeignContext.class);
		Feign.Builder builder = feign(context);

		if (!StringUtils.hasText(url)) {

			if (LOG.isInfoEnabled()) {
				LOG.info("For '" + name + "' URL not provided. Will try picking an instance via load-balancing.");
			}
			if (!name.startsWith("http")) {
				url = "http://" + name;
			}
			else {
				url = name;
			}
			url += cleanPath();
			return (T) loadBalance(builder, context, new HardCodedTarget<>(type, name, url));
		}
		if (StringUtils.hasText(url) && !url.startsWith("http")) {
			url = "http://" + url;
		}
		String url = this.url + cleanPath();
		Client client = getOptional(context, Client.class);
		if (client != null) {
			if (client instanceof FeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((FeignBlockingLoadBalancerClient) client).getDelegate();
			}
			if (client instanceof RetryableFeignBlockingLoadBalancerClient) {
				// not load balancing because we have a url,
				// but Spring Cloud LoadBalancer is on the classpath, so unwrap
				client = ((RetryableFeignBlockingLoadBalancerClient) client).getDelegate();
			}
			builder.client(client);
		}

		applyBuildCustomizers(context, builder);

		Targeter targeter = get(context, Targeter.class);
		return (T) targeter.target(this, builder, context, new HardCodedTarget<>(type, name, url));
	}

spring.factories

使用 spring.factories是springboot加载bean的一种方式,通常用于自动装配环境配置相关的类,和Configuration注解有异曲同工之妙。

其机制和java的SPI机制类似,不同的是其配置规则 : spring.factories以key,value的键值对列表存在。value是类的完全限定类名。类之间用逗号(,)分隔,后面跟着反斜杠(\)和换行符,以提高可读性。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.AutoConfigurationClass1,\
  com.example.AutoConfigurationClass2

@Configuration + @ComponentScan

@Configuration顾名思义就是我们的环境配置,标记SpringBoot中的配置类。其一般与其他Spring的相关配置一起使用。内部的话就是我们需要注入到spring容器的bean。

与@Conditiona簇的注解一起使用,用于在标识该类在满足某种规则后下生效。
在配置类中我们也可以添加@import注解导入某些类。

一般我们的配置类如果在启动类包路径下的话,SpringBoot可以自行装配,无需其他配置去扫描发现。

但是如果不是的话,则需要配置 @ComponentScan或者 spring.factories文件了。

一般我们开发一个框架的话,推荐使用spring.factories的方式,这样使用者导入相应的jar包(一般是spring-starter包)即可自动装配相应的bean。

如下的nacos中的服务发现配置:

@Configuration(proxyBeanMethods = false)
@ConditionalOnDiscoveryEnabled
@ConditionalOnNacosDiscoveryEnabled
public class NacosDiscoveryAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean
	public NacosDiscoveryProperties nacosProperties() {
		return new NacosDiscoveryProperties();
	}

	@Bean
	@ConditionalOnMissingBean
	public NacosServiceDiscovery nacosServiceDiscovery(
			NacosDiscoveryProperties discoveryProperties,
			NacosServiceManager nacosServiceManager) {
		return new NacosServiceDiscovery(discoveryProperties, nacosServiceManager);
	}

}

@Import直接导入类

在Spring框架中,@Import注解用于导入一个或多个配置类或普通的Java类,以便在当前的配置类中使用。它可以在@Configuration注解的类中使用,用于引入其他类的定义,以便在当前类中使用这些定义。

我们要思考下,一个配置类AConfiguration,为什么,要添加注解@@Import(BConfiguration.class)呢?

原因很简单,正常情况下,springBoot只会扫描main方法所在的包路径下的Bean 和 通过spring.factories进行注册发现自动装配到Spring IoC中去。

那如果一个BConfiguration配置类,不在上面的2种情况下,那怎么才能被加载创建Bean呢?

很简单,通过在配置类AConfiguration中,添加注解@@Import(BConfiguration.class),即可实现,BConfiguration中的bean实例,被自动加载创建。


下面,我们来看一个实例:


@AutoConfiguration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {

	@Bean
	@ConditionalOnMissingBean(name = "redisTemplate")
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<Object, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		return template;
	}

	@Bean
	@ConditionalOnMissingBean
	@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
	public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
		return new StringRedisTemplate(redisConnectionFactory);
	}

}

评论