[英]Dynamically injecting a JDK dynamic proxy as spring bean - but only if no other implementation is available
I have a situation where I want to look for interfaces annotated with a given annotation, then check if a matching implementation is available.我有一种情况,我想查找带有给定注释的接口,然后检查匹配的实现是否可用。 If not, I want to make a bean available that is actually a JDK proxy of the interface, essentially exactly what:如果没有,我想提供一个实际上是接口的 JDK 代理的 bean,本质上是什么:
@ConditionalOnMissingBean
would do, except without writing a factory method for each of those.会做,除非没有为每一个编写工厂方法。
I have code that is working "sometimes" - it seems extremely sensitive to the classloader structure, specifically, wether classes are loaded from a springboot fat jar (works) or wether some part is loaded from a separate classpath entry (doesnt work, mostly).我有“有时”工作的代码 - 它似乎对类加载器结构非常敏感,特别是,类是从 springboot fat jar 加载(有效)还是某些部分是从单独的类路径条目加载的(大多数情况下不起作用) .
here is what I am doing:这就是我正在做的事情:
@Service
@Order(value = Ordered.LOWEST_PRECEDENCE)
public class RemotingImportService implements BeanFactoryPostProcessor {
private static Log log = LogFactory.getLog(RemotingExportService.class);
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// scan classpath with classgraph, then:
for (ClassInfo classInfo : scanResult.getClassesWithAnnotation(MyAnnotation.class.getCanonicalName())) {
Class c = Class.forName(classInfo.getName());
if(beanFactory.getBeanNamesForType(c).length > 0) {
implFound = true;
log.info(c.getName()+" already has an implementation ... skipping");
continue;
}
// create proxy, then:
GenericBeanDefinition bdService = new GenericBeanDefinition();
bdService.setBeanClassName(classInfo.getName());
bdService.setInstanceSupplier(new ProxySupplier(proxy));
bdService.setLazyInit(true);
((DefaultListableBeanFactory) beanFactory).registerBeanDefinition(classInfo.getName(), bdService);
log.info(c.getName()+" has NO implementation ... proxy registerd");
}
in some cases, it seems that the beanfactory isn't finished, and在某些情况下,beanfactory 似乎还没有完成,并且
beanFactory.getBeanNamesForType()
returns an empty list, although it does find beans for that type later.返回一个空列表,尽管它稍后会找到该类型的 bean。 i am aware that messing with this is probably not ideal - but it would be nice to find a solution that plays nice with spring boot.我知道搞砸这可能并不理想——但如果能找到一个与 spring 引导配合得很好的解决方案会很好。
any suggestions on how to solve this?关于如何解决这个问题的任何建议? a way to mark a bean definition as "ConditionalOnMissingBean" would also be great.将 bean 定义标记为“ConditionalOnMissingBean”的方法也很棒。
You should use BeanPostProcessor
instead of BeanFactoryPostProcessor
您应该使用BeanPostProcessor
而不是BeanFactoryPostProcessor
BeanPostProcessor
is operating by assembled beans, while BeanFactoryPostProcessor
uses raw bean definitions. BeanPostProcessor
由组装的 bean 操作,而BeanFactoryPostProcessor
使用原始 bean 定义。 Read more here 在这里阅读更多
public class ConditionalOnMissingProcessor implements BeanPostProcessor, Ordered, ApplicationContextAware
{
private static final Logger LOG = Logger.getLogger(ConditionalOnMissingProcessor .class);
private ApplicationContext applicationContext;
// Ordering to last in chain.
@Override
public int getOrder()
{
return Ordered.LOWEST_PRECEDENCE;
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
/**
* Process each bean and inject proxy objects in fields marked with: {@ConditionalOnMissingBean}
*/
@Override
public Object postProcessBeforeInitialization(final Object bean, final String beanName) throws BeansException
{
LOG.debug("Processing bean: " + beanName);
final Class clazz = bean.getClass();
ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback()
{
@Override
public void doWith(final Field field)
{
try
{
if(field.isAnnotationPresent(ConditionalOnMissingBean.class))
{
ReflectionUtils.makeAccessible(field);
final String beanFieldName = field.getName();
...
// Find proper implementation in application context
// Or create a proxy.
// Inject proxy or impl into a bean
field.set(bean, <value>));
}
}
catch(IllegalAccessException e)
{
LOG.error("Cannot set " + field.getName() + " in " + beanName, e);
}
}
});
return bean;
}
UPD:升级版:
First of all, I have to say about disadvantages of your impl (IMHO): - you are trying to scan classpath to find all interfaces with @RemoteEndpoint
annotation, and, if current application context doesn't contain a bean that implemented this interface - create a proxy bean.首先,我不得不说一下您的 impl 的缺点(恕我直言):-您正在尝试扫描类路径以查找带有@RemoteEndpoint
注释的所有接口,并且,如果当前应用程序上下文不包含实现此接口的 bean-创建一个代理bean。 But what if I say, that not all interfaces marked with @RemoteEndpoint
should be taken into account?但是,如果我说,并非所有标有@RemoteEndpoint
的接口都应该考虑在内呢? Developer should explicitly mark those interfaces, and then you can create all needed beans (for example, developer makes a common-services library).开发人员应该明确标记这些接口,然后您可以创建所有需要的 bean(例如,开发人员制作了一个公共服务库)。 - probably you are specifying redudnant information, when marking impl class with @RemotingEndpoint(value=RandomService.class)
annotation. - 在使用@RemotingEndpoint(value=RandomService.class)
注释标记 impl class 时,您可能正在指定冗余信息。 You are already mentioned that when you implemented an interface.当你实现一个接口时,你已经提到过。
There are multiple ways for implementing your idea有多种方法可以实现您的想法
Using custom annotation on bean fields instead of @Autowired
.在 bean 字段上使用自定义注释而不是@Autowired
。
pros:优点:
BeanPostProcessor
.只需检查所有 bean 字段是否存在您的自定义注释,并使用BeanPostProcessor
注入依赖项(代理或 impl)。cons:缺点:
Using regular @Autowired
and @Value
annotations for injecting remote service proxy In this case you should use BeanFactoryPostProcessor
(as you already tried).使用常规@Autowired
和@Value
注释来注入远程服务代理在这种情况下,您应该使用BeanFactoryPostProcessor
(正如您已经尝试过的那样)。 You'll have to iterate over all bean definitions, collect a map of field names and interfaces of remote service proxies you'll have to register (dependencies meta info).您必须遍历所有 bean 定义,收集字段名称的 map 您必须注册的远程服务代理的接口(依赖项元信息)。 And next step is creating and registering beans using dependencies meta info (creating new ones only if there is no implementation bean in context).下一步是使用依赖元信息创建和注册 bean(仅当上下文中没有实现 bean 时才创建新的)。 Spring will autowire those fields later. Spring 稍后将自动连接这些字段。 But, those dependencies should be singleton beans.但是,这些依赖项应该是 singleton bean。
pros:优点:
cons:缺点:
Still using regular @Autowired
and @Value
annotations and BeanFactoryPostProcessor
, but instead of registering new beans, you should register a FactoryBean
for each interface @RemoteEndpoint
.仍然使用常规的@Autowired
和@Value
注释以及BeanFactoryPostProcessor
,但是您应该为每个接口@RemoteEndpoint
注册一个FactoryBean
,而不是注册新的 bean。
pros:优点:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.