ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

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

2021-11-11 18:03:21  阅读:228  来源: 互联网

标签:11 feng String Spring 接口 util BeanFactoryAware org import


文章目录

前言

说说问题出现的场景:

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

今天就处理这个问题!

1. 准备工作

1.1 创建一个普通的SpringBoot项目

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

  • BeanFactoryAware:在实现这个接口后,可以获取到 BeanFactory,可以用来手动注册 Bean。
  • ApplicationRunner:在Spring初始化后执行该实现的方法。
  • ApplicationContextAware: 实现该接口后,可以获取 applicationContext 对象。可以从 Spring 中获取Bean。

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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有