如何在Spring中使用类参数通过泛型类型创建singleton



假设我有一个泛型类型为的包装器

@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public class Wrapper<T> {
private final Class<T> wrappedClass;

public Wrapper(Class<T> wrappedClass) {
this.wrappedClass = wrappedClass;
}
}

我想将这个Wrapper用于许多类(例如>100(。是否可以让Spring为每个泛型类型创建包装器的singleton,并将泛型类作为参数传递给构造函数?例如,Spring必须始终注入相同的Wrapper<Foo>实例。如果可能的话,请用java代码配置举例,但不要用xml。

如果我理解正确,您希望根据一些bean(如Foo/Bar(遵守的一些标准动态添加包装器的bean。

这在spring中是一种高级的东西,但简而言之,您必须实现一个Bean工厂后处理器,该处理器将在启动期间由spring自动调用。

这是一个可以通过迭代所有";可访问";bean(像Foo/Bar和其他(,对于应该包装的bean,您将创建包装器的bean定义,尽管包装器本身不是bean。

我创建了一个简单的例子来说明这一点。在我的样本项目中,我把所有东西都放在"包"下;包装物":

@Wrappable
public class Foo {
}
@Wrappable
public class Bar {
}
public class ShouldNotBeWrapped {
}

请注意,我已经放置了一个注释CCD_;微分器";什么应该包装,什么不应该包装。注释的处理将在Bean工厂后处理器中完成。

注释其实没有什么特别的,它应该在运行时可以访问(spring是一个运行时框架,放在类上(:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Wrappable {
}

java配置将添加FooBarShouldNotBeWrapped作为Bean,还将添加Bean工厂后处理器,我将在下面描述:

@Configuration
public class WrappersJavaConfig {
@Bean
public Foo foo () {
return new Foo();
}
@Bean
public Bar bar () {
return new Bar();
}
@Bean
public ShouldNotBeWrapped shouldNotBeWrapped () {
return new ShouldNotBeWrapped();
}
@Bean
public WrappersEnrichmentBFPP wrappersEnrichmentBFPP () {
return new WrappersEnrichmentBFPP();
}
}

为了举例,Wrapper类本身有toString,但它与问题中的包装器没有太大区别:

public class Wrapper<T> {
private T wrapped;
public Wrapper(T wrapped) {
this.wrapped = wrapped;
}
@Override
public String toString() {
return "Wrapper for" + wrapped;
}
}

Main类将列出所有加载的bean,并获取它们的类+调用toString,这样我们就可以看到包装器的定义是正确的:

public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(WrappersJavaConfig.class);
String[] names = ctx.getBeanDefinitionNames();
for(String name : names) {
Object bean = ctx.getBean(name);
if(bean.getClass().getPackage().getName().startsWith("wrappers")) { 
System.out.println(ctx.getBean(name).getClass() + " ==> " + ctx.getBean(name));
}
}
}
}

旁注;如果";主方法中的条件是因为我不想打印自己弹簧加载的bean(infra stuff等(——只打印所有驻留在package中的bean;包装物";正如我上面提到的。

现在BeanFactoryPostProcessor是一个普通的bean,它在java配置中注册,看起来是这样的(您的实现可能不同,但想法是一样的(:

public class WrappersEnrichmentBFPP implements BeanFactoryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
String[] bddNames = beanFactory.getBeanDefinitionNames();
for(String bddName : bddNames) {
Object bean = beanFactory.getBean(bddName);
if(bean.getClass().isAnnotationPresent(Wrappable.class)) {
BeanDefinition wrappedBdd = BeanDefinitionBuilder.genericBeanDefinition(Wrapper.class)
.addConstructorArgReference(bddName)
.getBeanDefinition();
((BeanDefinitionRegistry)beanFactory).registerBeanDefinition("wrapperFor" + bddName, wrappedBdd);
}
}
}
}

所以我为每个循环一个接一个地获取所有的bean,然后我问bean是否有一个注释";可包装的";在CCD_ 9。如果有,就必须把它包起来。在这种情况下;人造的";为Wrapper定义bean,并添加一个构造函数,该构造函数将引用应该包装的bean。然后,我通过将bean定义添加到应用程序上下文中来注册它。

运行上面的代码,你会看到类似于我的输出:

class wrappers.WrappersJavaConfig$$EnhancerBySpringCGLIB$$f88f147d ==> wrappers.WrappersJavaConfig$$EnhancerBySpringCGLIB$$f88f147d@1283bb96
class wrappers.Foo ==> wrappers.Foo@74f0ea28
class wrappers.Bar ==> wrappers.Bar@f6efaab
class wrappers.ShouldNotBeWrapped ==> wrappers.ShouldNotBeWrapped@3c19aaa5
class wrappers.WrappersEnrichmentBFPP ==> wrappers.WrappersEnrichmentBFPP@3349e9bb
class wrappers.Wrapper ==> Wrapper forwrappers.Foo@74f0ea28
class wrappers.Wrapper ==> Wrapper forwrappers.Bar@f6efaab

如您所见,最后两行对应于为FooBar的相同实例创建的包装bean,但没有为ShouldNotBeWrappedbean 创建任何内容

所使用的API有点晦涩难懂,看起来已经过时了,但它仍然是相当先进的东西,在spring容器基础设施本身的级别上工作。话虽如此,有很多关于BeanFactoryPostProcessor-s的教程。

由于使用BFPP不是一项常见的任务,尽管我已经提供了解决方案,但我看不到它的任何实际用途,因此不能使用包装器;取而代之的是";FooBar类中,没有它们的API等。也许你可以解释为什么你需要在一些bean上使用包装器。通常,人们使用Aspects/BeanPostProcessors(不是BFPP,而是BPP(将类封装到动态代理(cglib/java.lang.proxy(中,并添加额外的行为,如@Transactional、缓存处理等,这些都是在春季用BeanPostProcessors实现的,所以也要考虑检查这个方向。

这是可能的,事实上是spring的一个特性

Spring可以为您的依赖项注入正确的泛型类型。以下示例来自spring文档。

假设你有一个接口

public interface Store<T>{...}

和两颗豆子。一个实现Store,一个实现Store。

@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}

您可以使用正确的类型参数声明类型,spring将为您注入正确的bean。

@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean

最新更新