- 前言
- 1. 准备工作
- 1.1 创建一个普通的SpringBoot项目
- 1.2 项目目录结构
- 2. 配置文件 application.properties
- 3. org.feng.util中的类内容
- 3.1 ApplicationRunnerSupport
- 3.2 BeanFactorySupport
- 3.3 Business
- 3.4 BusinessType
- 3.5 CommonsUtil
- 3.6 SpringUtils
- 4. 测试运行
- 4.1 BusinessService
- 4.2 AddServiceImpl
- 4.3 UpdateServiceImpl
- 5. 启动测试
- 5.1 测试类
- 5.2 控制台输出
- 6. 总结
说说问题出现的场景:
在区分业务场景的前提下,有一种多租户的情景,就是每种业务实现都会有多个。
一般出现在多平台的对接的时候。
这个时候,需要将某一接口下的多个实现类注入到Spring容器中进行管理。
但是,获取 Bean 和使用的时候,却不太好使。可能还很麻烦。
今天就处理这个问题!
1. 准备工作 1.1 创建一个普通的SpringBoot项目引入依赖需要 lombok和web的starter。
然后是事先了解:
- BeanFactoryAware:在实现这个接口后,可以获取到 BeanFactory,可以用来手动注册 Bean。
- ApplicationRunner:在Spring初始化后执行该实现的方法。
- ApplicationContextAware: 实现该接口后,可以获取 applicationContext 对象。可以从 Spring 中获取Bean。
- PostConstruct:该注解使用在非静态方法上,表示在依赖注入之后自动执行。
其中,service 包是为了验证功能写的,所有的要用到的类、接口、注解都放在了这个 org.feng.util包中。
文件中定义的是需要扫描的包名,多个包时使用英文逗号隔开。
business.scan.path = org.feng.service.add,org.feng.service.update3. 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;
@Slf4j
@Order(1)
@Component
public class ApplicationRunnerSupport implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) {
// 获得注册 bean 时使用的bean命名
Set 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;
@Slf4j
@Configuration
public class BeanFactorySupport implements BeanFactoryAware {
private static BeanFactory beanFactory;
private static final Set 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 getBeanNames() {
return BEAN_NAMES;
}
@PostConstruct
public void beforeInit() throws ClassNotFoundException {
Objects.requireNonNull(path);
String[] split = path.split(",");
for (String packagePath : split) {
if (!"".equals(packagePath)) {
register(packagePath);
}
}
}
public static void register(String path) throws ClassNotFoundException {
DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory) getBeanFactory();
ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false);
// 扫描带有自定义注解的类
provider.addIncludeFilter(new AnnotationTypeFilter(Business.class));
Set 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;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Business {
String prefix() default "";
String business() default "";
}
3.4 BusinessType
package org.feng.util;
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;
public class CommonsUtil {
public static final Map 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;
@Slf4j
@Configuration
public class SpringUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringUtils.applicationContext = applicationContext;
}
public static T getBean(String beanName, Class clazz) {
return applicationContext.getBean(beanName, clazz);
}
public static Object getBean(String beanName) {
return applicationContext.getBean(beanName);
}
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包中:
定义一个接口,两个实现类。
package org.feng.service;
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;
@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;
@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 增加的业务 AddServiceImpl6. 总结
本次测试圆满成功,在一接口,多实现的情境下,可以自主控制注入 Bean 到Spring中,并使用工具类获取。



