Spring 中 BeanFactoryAware 的实战之注册一接口多实现场景
作者:互联网
文章目录
前言
说说问题出现的场景:
在区分业务场景的前提下,有一种多租户的情景,就是每种业务实现都会有多个。
一般出现在多平台的对接的时候。
这个时候,需要将某一接口下的多个实现类注入到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