Spring:程序化创建MessageSource并动态调用Bean的问题
嘿,你这个思路其实已经走对了大半——通过BeanFactoryAware注册客户专属的MessageSource单例并存入Map,现在的问题就是怎么优雅地动态获取这些实例对吧?下面给你几个实用的方案:
方案一:直接注入MessageSourceConfig调用方法
这是最直接的方式,因为你已经把所有客户的MessageSource存在了messageSourceMap里,其他业务组件只需要注入你的配置类,直接调用companyMessageSource方法就能拿到对应实例:
@Service public class SomeBusinessService { @Autowired private MessageSourceConfig messageSourceConfig; public String getLocalizedMessage(Company company, String code, Object... args) { MessageSource messageSource = messageSourceConfig.companyMessageSource(company); return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); } }
这种方式的好处是完全复用你已经写好的逻辑,不需要额外改动配置类,上手最快。
方案二:通过BeanFactory根据BeanName直接获取
你在注册单例的时候已经给每个客户的MessageSource指定了{slug}_messageSource的BeanName,所以可以直接通过BeanFactory来获取实例,甚至不需要依赖那个Map:
方式1:在配置类里新增根据Slug获取的方法
// 在MessageSourceConfig里新增 public MessageSource getMessageSourceBySlug(String slug) { String beanName = slug + "_messageSource"; return beanFactory.containsBean(beanName) ? beanFactory.getBean(beanName, MessageSource.class) : messageSource(); }
然后同样注入配置类调用这个方法即可。
方式2:在业务组件中直接注入BeanFactory
@Service public class SomeBusinessService { @Autowired private BeanFactory beanFactory; @Autowired private MessageSource defaultMessageSource; public String getLocalizedMessage(String companySlug, String code, Object... args) { String beanName = companySlug + "_messageSource"; MessageSource messageSource = beanFactory.containsBean(beanName) ? beanFactory.getBean(beanName, MessageSource.class) : defaultMessageSource; return messageSource.getMessage(code, args, LocaleContextHolder.getLocale()); } }
这种方式的优势是不需要维护那个messageSourceMap,直接利用Spring的Bean容器管理,避免Map和Bean容器不一致的风险。
方案三:封装成独立的MessageSourceResolver组件(推荐)
为了让代码更符合单一职责原则,你可以把动态获取MessageSource的逻辑抽成一个独立的组件,这样业务代码不用直接依赖配置类,扩展性更好:
@Component public class CompanyMessageSourceResolver { private final BeanFactory beanFactory; private final MessageSource defaultMessageSource; private final ICompanyService companyService; // 构造注入(Spring 4.3+支持) public CompanyMessageSourceResolver(BeanFactory beanFactory, @Qualifier("messageSource") MessageSource defaultMessageSource, ICompanyService companyService) { this.beanFactory = beanFactory; this.defaultMessageSource = defaultMessageSource; this.companyService = companyService; } public MessageSource getMessageSource(Company company) { return getMessageSourceBySlug(company.getSlug()); } public MessageSource getMessageSourceBySlug(String slug) { String beanName = slug + "_messageSource"; return beanFactory.containsBean(beanName) ? beanFactory.getBean(beanName, MessageSource.class) : defaultMessageSource; } // 甚至可以直接封装getMessage方法,让业务层更省心 public String getMessage(Company company, String code, Object... args) { MessageSource ms = getMessageSource(company); return ms.getMessage(code, args, LocaleContextHolder.getLocale()); } }
然后业务组件只需要注入这个Resolver即可:
@Service public class SomeBusinessService { @Autowired private CompanyMessageSourceResolver messageSourceResolver; public String getSomeLocalizedContent(Company company) { return messageSourceResolver.getMessage(company, "some.business.code"); } }
为什么不能把companyMessageSource声明为Bean?
顺便解释下你遇到的这个疑问:Spring的Bean定义是无状态或依赖注入的,带参数的方法没法直接注册为Bean——因为Spring不知道每次调用时的Company参数从哪里来,它没办法提前实例化或者管理这个Bean的生命周期。所以这种动态参数的获取逻辑,只能通过方法调用的方式实现,而不能直接作为Bean注册。
内容的提问来源于stack exchange,提问作者MarioC

