在运行时更换弹簧容器内的bean
假设我在Spring容器中定义了一个bean(例如BeanA),并将这个bean注入到一个对象中。 (例如BeanAUser)
在运行期间,我可以使用另一个bean实例来替换弹簧容器内的原始BeanA吗? 并且还将这个新的bean实例重新注入BeanAUser以替换原来的BeanA?
使用代理可以轻松实现。 创建一个委托的接口的委托实现和它所委托的切换对象。
@Component("BeanA") public class MyClass implements MyInterface { private MyInterface target; public void setTarget(MyInterface target) { this.target = target; } // now delegating implementation of MyInterface methods public void method1(..) { this.target.method1(..); } .. }
我这样做的方法是使用一个名为任意方法替换的系统。
创建一个实现org.springframework.beans.factory.support.MethodReplacer
的类,这会强制你创建一个像这样的方法
public Object reimplement(Object o, Method m, Object[] args) throws Throwable
参数表示以下内容:
- o – 您正在替换方法的bean实例
- m – 我们正在取代的方法元
- args – 提供的方法参数(如果有)
所以我想你的课程看起来像下面这样
public BeanAUserHelper implements MethodReplacer { public Object reimplement(Object o, Method m, Object[] args) throws Throwable { if (some expression){ return beanA; } else { return beanB; } } }
在bean配置中,然后指示Spring替换getBeanX()
上的getBeanX()
方法
我希望我能正确理解你的问题:)
Spring引入了新的RefreshScope来在运行时替换bean。 在内部,如mrembisz的答案中所述创建代理。
@RefreshScope @Component public class MyBean { ... }
假设mrembisz的答案中的MyClass
不是最终的,那么就不必手动实现装饰器模式,并且可以使用BeanPostProcessor
自动实现它。 首先定义用于注入新委托实现的扩展接口:
public interface Wrapper extends MyInterface { void setTarget(MyInterface target); }
然后创建BeanPostProcessor
, MyInterface
所有实现包装到CGLIB代理。 Proxy充当MyClass
(可以将其注入MyClass
类型的字段)和Wrapper
(使其能够更改目标)。 代理将所有原始调用重定向到MyClass
目标(最初设置为Spring中声明的值),调用Wrapper.setTarget
导致目标更改。
@Component public static class MyPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Object result = bean; if (bean instanceof MyInterface) { final MyInterface myInterface = (MyInterface)bean; Class extends MyInterface> clazz = myInterface.getClass(); if (!isFinal(clazz.getModifiers())) { result = Enhancer.create(clazz, new Class[] {MyInterface.class}, new MethodInterceptor() { private MyInterface target = myInterface; public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { if (method.getName().equals("setTarget") && method.getDeclaringClass().equals(Wrapper.class) && method.getParameterCount() == 1 && method.getParameterTypes()[0].equals(MyInterface.class)) { this.target = (MyInterface)args[0]; return null; } else { Object result = proxy.invoke(this.target, args); return result; } } }); } } return result; } }
简单的想法是:在Spring中定义bean,因为它是普通的bean,在初始化后调整它。
有一些方法可以在创建之前操纵spring上下文。
-
一种方法是,使用GenericApplicationContext和GenericBeanDefinition类来操作上下文。 以下示例代码显示此解决方案:
GenericApplicationContext context = new GenericApplicationContext(); XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(context); xmlReader.loadBeanDefinitions(new ClassPathResource(original-context)); BeanDefinitionRegistry registry = ((BeanDefinitionRegistry) context); GenericBeanDefinition myBean = new GenericBeanDefinition(); myBean.setBeanClass(MyCustomClass.class); myBean.getPropertyValues().add("name", "My-Name"); registry.registerBeanDefinition("my_bean_name", myBean); context.refresh();
通过此代码段,您可以添加或删除或更改它创建的bean。
- 第二种解决方案是在Spring中使用BeanPostProcessor机制。 对于详细信息,请参阅此URL: http ://www.roseindia.net/tutorial/spring/spring3/ioc/beanpostprocessor.html或者为什么在此Spring 3.0身份validation示例中除了UserDetailsService之外还需要此BeanPostProcessor?
你在这里过了一条细线。 基本上你试图将应用程序逻辑放在Spring容器中。 避免使用配置文件进行编程,并仅使用Spring(或任何DI框架)进行基本连线。
@mrembisz提供的代理建议是首选的。 这样,应用程序逻辑和配置就分开了。