其他分享
首页 > 其他分享> > Spring 中 BeanFactoryAware 的实战之注册一接口多实现场景

Spring 中 BeanFactoryAware 的实战之注册一接口多实现场景

作者:互联网

文章目录

前言

说说问题出现的场景:

在区分业务场景的前提下,有一种多租户的情景,就是每种业务实现都会有多个。
一般出现在多平台的对接的时候。
这个时候,需要将某一接口下的多个实现类注入到Spring容器中进行管理。
但是,获取 Bean 和使用的时候,却不太好使。可能还很麻烦。

今天就处理这个问题!

1. 准备工作

1.1 创建一个普通的SpringBoot项目

引入依赖需要 lombok和web的starter。
然后是事先了解:

1.2 项目目录结构

在这里插入图片描述
其中,service 包是为了验证功能写的,所有的要用到的类、接口、注解都放在了这个 org.feng.util包中。

2. 配置文件 application.properties

文件中定义的是需要扫描的包名,多个包时使用英文逗号隔开。

business.scan.path = org.feng.service.add,org.feng.service.update

3. org.feng.util中的类内容

3.1 ApplicationRunnerSupport

package org.feng.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Set;

/**
 * 系统启动完成后,执行一些操作<br>
 * 将自己定义的扫描注册到Spring中的实例取出放到一个全局 Map 中。
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 15时39分
 * @see Order 指定顺序,当有多个runner是指定。
 * @see ApplicationRunner Spring提供的接口,帮助在系统启动后操作一些业务
 */
@Slf4j
@Order(1)
@Component
public class ApplicationRunnerSupport implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) {
        // 获得注册 bean 时使用的bean命名
        Set<String> beanNames = BeanFactorySupport.getBeanNames();
        for (String beanName : beanNames) {
            Object bean = SpringUtils.getBean(beanName);
            CommonsUtil.BEANS.put(beanName, bean);
            log.info("注册Bean {} 成功!", bean);
        }
        log.info("注册结束,共注册 {} 个 Bean", CommonsUtil.BEANS.size());
        BeanFactorySupport.getBeanNames().clear();
    }
}

3.2 BeanFactorySupport

package org.feng.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

/**
 * Bean 注册
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 14时29分
 */
@Slf4j
@Configuration
public class BeanFactorySupport implements BeanFactoryAware {
    private static BeanFactory beanFactory;
    /**
     * 收集扫描到的类对象注入到 Spring 中时的命名
     */
    private static final Set<String> BEAN_NAMES = new HashSet<>(16);

    /**
     * 指定包名,多个包名时用英文逗号隔开
     */
    @Value("${business.scan.path}")
    private String path;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        BeanFactorySupport.beanFactory = beanFactory;
    }

    public static BeanFactory getBeanFactory() {
        return BeanFactorySupport.beanFactory;
    }

    public static Set<String> getBeanNames() {
        return BEAN_NAMES;
    }

    /**
     * 将配置中的包名切分,
     *
     * @throws ClassNotFoundException 可能找不到类
     */
    @PostConstruct
    public void beforeInit() throws ClassNotFoundException {
        Objects.requireNonNull(path);
        String[] split = path.split(",");

        for (String packagePath : split) {
            if (!"".equals(packagePath)) {
                register(packagePath);
            }
        }
    }

    /**
     * 注册指定包路径下的包含 Business 注解的类到 Spring 容器内
     *
     * @param path 包路径
     * @throws ClassNotFoundException 可能找不到类
     */
    public static void register(String path) throws ClassNotFoundException {
        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
        // 扫描带有自定义注解的类
        provider.addIncludeFilter(new AnnotationTypeFilter(Business.class));
        Set<BeanDefinition> scanList = provider.findCandidateComponents(path);
        if (CollectionUtils.isEmpty(scanList)) {
            log.error("未扫描到 Bean 资源....");
            return;
        }

        // 注册Bean
        for (BeanDefinition beanDefinition : scanList) {
            Business business = Class.forName(beanDefinition.getBeanClassName()).getAnnotation(Business.class);
            String prefix = business.prefix();
            String beanName = String.join("-", prefix, beanDefinition.getBeanClassName(), business.business());
            log.info("注册:{}", beanName);
            BEAN_NAMES.add(beanName);
            beanFactory.registerBeanDefinition(beanName, beanDefinition);
        }
    }
}

3.3 Business

package org.feng.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 业务注解
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 14时40分
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Business {
    /**
     * BeanName 的前缀
     *
     * @return 自主指定
     */
    String prefix() default "";

    /**
     * 业务类型
     *
     * @return BusinessType 中的常量
     */
    String business() default "";
}

3.4 BusinessType

package org.feng.util;

/**
 * 业务枚举<br>
 * 需要扩展时,继承此接口即可。
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 16时39分
 */
public interface BusinessType {
    String ADD = "add";
    String UPDATE = "update";
    String DELETE = "delete";
    String SEARCH = "search";
}

3.5 CommonsUtil

package org.feng.util;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 公共工具类:定义常量
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 14时56分
 */
public class CommonsUtil {
    public static final Map<String, Object> BEANS = new ConcurrentHashMap<>();
}

3.6 SpringUtils

package org.feng.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;

import java.util.Objects;

/**
 * Spring 的工具类,获取bean实例
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 14时17分
 * @see ApplicationContextAware
 */
@Slf4j
@Configuration
public class SpringUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

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

    /**
     * 通过bean的名字、类型获取bean实例
     *
     * @param beanName bean在spring中的名字
     * @param clazz    bean的类型
     * @param <T>      实例的类型,通过clazz来确定
     * @return spring容器中的bean实例
     */
    public static <T> T getBean(String beanName, Class<T> clazz) {
        return applicationContext.getBean(beanName, clazz);
    }

    /**
     * 通过bean的名字、类型获取bean实例
     *
     * @param beanName bean在spring中的名字
     * @return spring容器中的bean实例
     */
    public static Object getBean(String beanName) {
        return applicationContext.getBean(beanName);
    }

    /**
     * 获取业务实现的 Bean。
     *
     * @param prefix       前缀,没有时,可以为空字符串
     * @param beanName     bean名,其实是包+类名
     * @param businessType 业务类型, {@link BusinessType}中的常量;没有时,可以为空字符串
     * @return 对应的bean
     */
    public static Object getBusinessBean(@NonNull String prefix, @NonNull String beanName, @NonNull String businessType) {
        String beanKey = String.join("-", prefix, beanName, businessType);
        Object bean = CommonsUtil.BEANS.get(beanKey);
        if (Objects.isNull(bean)) {
            log.debug("参数错误:prefix={},beanName={},businessType={}", prefix, beanName, businessType);
            throw new IllegalArgumentException("参数错误,无法找到对应的 Bean");
        }
        return bean;
    }
}

4. 测试运行

在 service包中:
在这里插入图片描述
定义一个接口,两个实现类。

4.1 BusinessService

package org.feng.service;

/**
 * 业务接口
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 16时48分
 */
public interface BusinessService {
    String business();
}

4.2 AddServiceImpl

package org.feng.service.add;

import org.feng.service.BusinessService;
import org.feng.util.Business;
import org.feng.util.BusinessType;

/**
 * 增加的业务
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 16时50分
 */
@Business(prefix = "Feng", business = BusinessType.ADD)
public class AddServiceImpl implements BusinessService {
    @Override
    public String business() {
        return "增加的业务 AddServiceImpl";
    }
}

4.3 UpdateServiceImpl

package org.feng.service.update;

import org.feng.service.BusinessService;
import org.feng.util.Business;
import org.feng.util.BusinessType;

/**
 * 更新的业务
 *
 * @version V1.0
 * @author: fengjinsong
 * @date: 2021年11月11日 16时55分
 */
@Business(prefix = "Feng", business = BusinessType.UPDATE)
public class UpdateServiceImpl implements BusinessService {
    @Override
    public String business() {
        return "更新的业务 UpdateServiceImpl";
    }
}

5. 启动测试

5.1 测试类

package org.feng;

import org.feng.service.add.AddServiceImpl;
import org.feng.util.BusinessType;
import org.feng.util.SpringUtils;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootDemoApplicationTests {
    @Test
    void contextLoads() {
        Object businessBean = SpringUtils.getBusinessBean("Feng", AddServiceImpl.class.getName(), BusinessType.ADD);
        AddServiceImpl businessBean1 = (AddServiceImpl) businessBean;
        System.out.println(businessBean1.business());
    }
}

5.2 控制台输出

2021-11-11 17:28:23.674  INFO 19096 --- [           main] org.feng.util.BeanFactorySupport         : 注册:Feng-org.feng.service.add.AddServiceImpl-add
2021-11-11 17:28:23.676  INFO 19096 --- [           main] org.feng.util.BeanFactorySupport         : 注册:Feng-org.feng.service.update.UpdateServiceImpl-update
2021-11-11 17:28:24.259  INFO 19096 --- [           main] org.feng.SpringbootDemoApplicationTests  : Started SpringbootDemoApplicationTests in 1.339 seconds (JVM running for 2.104)
2021-11-11 17:28:24.262  INFO 19096 --- [           main] org.feng.util.ApplicationRunnerSupport   : 注册Bean org.feng.service.update.UpdateServiceImpl@18d900 成功!
2021-11-11 17:28:24.263  INFO 19096 --- [           main] org.feng.util.ApplicationRunnerSupport   : 注册Bean org.feng.service.add.AddServiceImpl@9f5761 成功!
2021-11-11 17:28:24.263  INFO 19096 --- [           main] org.feng.util.ApplicationRunnerSupport   : 注册结束,共注册 2 个 Bean
增加的业务 AddServiceImpl

6. 总结

本次测试圆满成功,在一接口,多实现的情境下,可以自主控制注入 Bean 到Spring中,并使用工具类获取。

标签:11,feng,String,Spring,接口,util,BeanFactoryAware,org,import
来源: https://blog.csdn.net/FBB360JAVA/article/details/121272551