I'm trying to port the JSF #ViewScoped annotation to CDI. The reason is more educational rather than based on need. I chose this particular scope mainly due to the lack of a better concrete example of a custom scope one might want to implement in CDI.
That said, my starting point was Porting the #ViewScoped JSF annotation to CDI. But, this implementation does not take into account a seemingly very important responsibility of Context (i.e. destroying) mentioned in the API:
The context object is responsible for creating and destroying contextual instances by calling operations of Contextual. In particular, the context object is responsible for destroying any contextual instance it creates by passing the instance to Contextual.destroy(Object, CreationalContext). A destroyed instance must not subsequently be returned by get(). The context object must pass the same instance of CreationalContext to Contextual.destroy() that it passed to Contextual.create() when it created the instance.
I decided to add this functionality by having my Context object:
keep track of what Contextual objects it creates for which UIViewRoots;
implement the ViewMapListener interface and register itself as listener for each UIViewRoot by calling UIViewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, this);
destroy any created Contextuals when the ViewMapListener.processEvent(SystemEvent event) is called and unregister itself from that UIViewRoot.
Here's my Context implementation:
package com.example;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.enterprise.context.spi.Context;
import javax.enterprise.context.spi.Contextual;
import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.Bean;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.ViewMapListener;
public class ViewContext implements Context, ViewMapListener {
private Map<UIViewRoot, Set<Disposable>> state;
public ViewContext() {
this.state = new HashMap<UIViewRoot, Set<Disposable>>();
}
// mimics a multimap put()
private void put(UIViewRoot key, Disposable value) {
if (this.state.containsKey(key)) {
this.state.get(key).add(value);
} else {
HashSet<Disposable> valueSet = new HashSet<Disposable>(1);
valueSet.add(value);
this.state.put(key, valueSet);
}
}
#Override
public Class<? extends Annotation> getScope() {
return ViewScoped.class;
}
#Override
public <T> T get(final Contextual<T> contextual,
final CreationalContext<T> creationalContext) {
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
FacesContext ctx = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = ctx.getViewRoot();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
final T instance = contextual.create(creationalContext);
viewMap.put(name, instance);
// register for events
viewRoot.subscribeToViewEvent(
PreDestroyViewMapEvent.class, this);
// allows us to properly couple the right contaxtual, instance, and creational context
this.put(viewRoot, new Disposable() {
#Override
public void dispose() {
contextual.destroy(instance, creationalContext);
}
});
return instance;
}
} else {
return null;
}
}
#Override
public <T> T get(Contextual<T> contextual) {
if (contextual instanceof Bean) {
Bean bean = (Bean) contextual;
String name = bean.getName();
FacesContext ctx = FacesContext.getCurrentInstance();
UIViewRoot viewRoot = ctx.getViewRoot();
Map<String, Object> viewMap = viewRoot.getViewMap();
if (viewMap.containsKey(name)) {
return (T) viewMap.get(name);
} else {
return null;
}
} else {
return null;
}
}
// this scope is only active when a FacesContext with a UIViewRoot exists
#Override
public boolean isActive() {
FacesContext ctx = FacesContext.getCurrentInstance();
if (ctx == null) {
return false;
} else {
UIViewRoot viewRoot = ctx.getViewRoot();
return viewRoot != null;
}
}
// dispose all of the beans associated with the UIViewRoot that fired this event
#Override
public void processEvent(SystemEvent event)
throws AbortProcessingException {
if (event instanceof PreDestroyViewMapEvent) {
UIViewRoot viewRoot = (UIViewRoot) event.getSource();
if (this.state.containsKey(viewRoot)) {
Set<Disposable> valueSet = this.state.remove(viewRoot);
for (Disposable disposable : valueSet) {
disposable.dispose();
}
viewRoot.unsubscribeFromViewEvent(
PreDestroyViewMapEvent.class, this);
}
}
}
#Override
public boolean isListenerForSource(Object source) {
return source instanceof UIViewRoot;
}
}
Here's the Disposable interface:
package com.example;
public interface Disposable {
public void dispose();
}
Here's the scope annotation:
package com.example;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.enterprise.context.NormalScope;
#Inherited
#NormalScope
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD,
ElementType.FIELD, ElementType.PARAMETER})
public #interface ViewScoped {
}
Here's the CDI extension declaration:
package com.example;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
public class CustomContextsExtension implements Extension {
public void afterBeanDiscovery(#Observes AfterBeanDiscovery event) {
event.addContext(new ViewContext());
}
}
I added the javax.enterprise.inject.spi.Extension file under META-INF/services containing com.example.CustomContextsExtension to properly register the above with CDI.
I can now make beans like (notice the use of the custom #ViewScoped implementation.):
package com.example;
import com.concensus.athena.framework.cdi.extension.ViewScoped;
import java.io.Serializable;
import javax.inject.Named;
#Named
#ViewScoped
public class User implements Serializable {
...
}
The beans are created properly and properly injected into JSF pages (i.e. the same instance is returned per view, new ones are created only when the view is created, the same instances are injected over multiple requests to the same view). How do I know? Imagine the above code littered with debugging code which I purposefully stripped out for clarity and since this is already a huge post.
The problem is that my ViewContext.isListenerForSource(Object source) and ViewContext.processEvent(SystemEvent event) are never called. I was expecting that at least upon session expiration those events would be called, since the view map is stored in the session map (correct?). I set the session timeout to 1 minute, waited, saw the timeout happen, but my listener was still not called.
I also tried adding the following to my faces-config.xml (mostly out of the lack of ideas):
<system-event-listener>
<system-event-listener-class>com.example.ViewContext</system-event-listener-class>
<system-event-class>javax.faces.event.PreDestroyViewMapEvent</system-event-class>
<source-class>javax.faces.component.UIViewRoot</source-class>
</system-event-listener>
Finally, my environment is JBoss AS 7.1.1 with Mojarra 2.1.7.
Any clues would be greatly appreciated.
EDIT: Further Investigation.
PreDestroyViewMapEvent doesn't seem to be fired at all while PostConstructViewMapEvent is fired as expected - every time a new view map is created, specifically during UIViewRoot.getViewMap(true). The documentation states that PreDestroyViewMapEvent should be fired every time clear() is called on the view map. That leaves to wonder - is clear() required to be called at all? If so, when?
The only place in the documentation that I was able to find such a requirement is in FacesContext.setViewRoot():
If the current UIViewRoot is non-null, and calling equals() on the
argument root, passing the current UIViewRoot returns false, the clear
method must be called on the Map returned from UIViewRoot#getViewMap.
Does this ever happen in the normal JSF lifecycle, i.e. without programmatically calling UIViewRoot.setViewMap()? I can't seem to find any indication.
This is related to an issue with the JSF spec that is being fixed in the JSF2.2 spec, see here. Also, I created an issue with Apache DeltaSpike so they may try to fix it, see here. If it's fixed in DeltaSpike, then it may end up being merged into CODI and / or Seam as well.
The view map is stored in a LRU map, because you never know which view will be post back. Unfortunately, the PreDestroyViewMapEvent is not called before removing from this map.
A workaround is to reference your object from within a WeakReference. You can use ReferenceQueue or check the reference when to call your destruction code.
Related
Since Jackson's hibernate5-module not working for me. I'm trying to implement my own lazy property filter. I implemented custom annotation introspection successfully.
But when I apply my custom serializer, #JsonIgnoreProperties is ignored.
#Entity
class Call {
#OneToMany(mappedBy = "call")
#JsonIgnoreProperties("call")
List<CallEvent> events;
}
#Entity
class CallEvent {
#ManyToOne(fetch = FetchType.LAZY)
Call call;
}
public class LazyValueIntrospector extends JacksonAnnotationIntrospector {
#Override
public Object findSerializer(Annotated a) {
var yes = a.hasAnnotation(ManyToOne.class)
|| a.hasAnnotation(Basic.class)
|| a.hasAnnotation(OneToMany.class)
|| a.hasAnnotation(OneToOne.class);
if (yes) {
return LazyValueSerializer.class;
}
return super.findSerializer(a);
}
}
public class LazyValueSerializer extends JsonSerializer<Object> {
#Override
public boolean isEmpty(SerializerProvider provider, Object value) {
return value == null || !Hibernate.isInitialized(value);
}
#Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeObject(value);
}
}
Explanation:
LazyValueIntrospector.findSerializer detects possible lazy properties.
Hibernate.isInitialized tells me the value is initialized or not.
gen.writeObject(value); writes if property is initialized.
The problem is gen.writeObject(value); method call is ignoring #JsonIgnoreProperties("call") annotation.
The question is:
How to apply #JsonIgnoreProperties("call") annotation in my custom serializer?
Ps: spring.jackson.defaultPropertyInclusion=non_empty property applied globally. Which allows isEmpty checking.
As we are ignoring only one property, try giving #JsonIgnore over the call property inside the CallEvent entity above Call.
Example:
#Entity
class CallEvent {
#ManyToOne(fetch = FetchType.LAZY)
#JsonIgnore
Call call;
}
Without actual example of where #JsonIgnoreProperties is ignored it is hard to say. But I think that the custom serializer would need to delegate to the original serializer and not replace it; there's quite a bit more that is needed to support various other features.
To do that you cannot register serializer the way like shown here, but instead replace it using one of methods in BeanSerializerModifier (and need to register that modifier with ObjectMapper) -- that way you get the "real" serializer to delegate to.
Usually implementations also need to implement createContextual() (from ContextSerializer) which needs to be passed to the original ("delegate") serializer.
You may want to have a look at how serializers are implemented in Hibernate module itself.
So, Since the question is how to apply jackson's own behaviors in the custom bean serializer. I found the answer for myself.
extend from BeanSerializerBase, not BeanSerializer.
override with* methods. Such as withByNameInclusion and withProperties
This way, Jackson calls appropriate methods when it's needed.
#Override
public JsonSerializer<Object> unwrappingSerializer(NameTransformer unwrapper) {
return new LazyBeanUnwrappingSerializer(this, unwrapper);
}
#Override
protected BeanSerializerBase withProperties(BeanPropertyWriter[] properties, BeanPropertyWriter[] filteredProperties) {
return new LazyBeanSerializer(this, properties, filteredProperties);
}
#Override
protected BeanSerializerBase withByNameInclusion(Set<String> toIgnore, Set<String> toInclude) {
return new LazyBeanSerializer(this, toIgnore, toInclude);
}
#Override
public BeanSerializerBase withObjectIdWriter(ObjectIdWriter objectIdWriter) {
return new LazyBeanSerializer(this, objectIdWriter, _propertyFilterId);
}
#Override
public BeanSerializerBase withFilterId(Object filterId) {
return new LazyBeanSerializer(this, _objectIdWriter, filterId);
}
#Override
protected BeanSerializerBase asArraySerializer() {
throw new RuntimeException("Array serializer no supported");
}
I have a class (OmeletteMaker) that contains an injected field (Vegetable). I would like to write a producer that instantiates an injected object of this class. If I use 'new', the result will not use injection. If I try to use a WeldContainer, I get an exception, since OmeletteMaker is #Alternative. Is there a third way to achieve this?
Here is my code:
#Alternative
public class OmeletteMaker implements EggMaker {
#Inject
Vegetable vegetable;
#Override
public String toString() {
return "Omelette: " + vegetable;
}
}
a vegetable for injection:
public class Tomato implements Vegetable {
#Override
public String toString() {
return "Tomato";
}
}
main file
public class CafeteriaMainApp {
public static WeldContainer container = new Weld().initialize();
public static void main(String[] args) {
Restaurant restaurant = (Restaurant) container.instance().select(Restaurant.class).get();
System.out.println(restaurant);
}
#Produces
public EggMaker eggMakerGenerator() {
return new OmeletteMaker();
}
}
The result I get is "Restaurant: Omelette: null", While I'd like to get "Restaurant: Omelette: Tomato"
If you provide OmeletteMaker yourself, its fields will not be injected by the CDI container. To use #Alternative, don't forget specifying it in the beans.xml and let the container instantiate the EggMaker instance:
<alternatives>
<class>your.package.path.OmeletteMaker</class>
</alternatives>
If you only want to implement this with Producer method then my answer may be inappropriate. I don't think it is possible (with standard CDI). The docs says: Producer methods provide a way to inject objects that are not beans, objects whose values may vary at runtime, and objects that require custom initialization.
Thanks Kukeltje for pointing to the other CDI question in comment:
With CDI extensions like Deltaspike, it is possible to inject the fields into an object created with new, simply with BeanProvider#injectFileds. I tested this myself:
#Produces
public EggMaker eggMakerProducer() {
EggMaker eggMaker = new OmeletteMaker();
BeanProvider.injectFields(eggMaker);
return eggMaker;
}
I'm new to dart. Currently, working on a mobile app through flutter. I have a Helper class which has some common methods which I've planned throughout the app. I've included that Helper class in another class. But, can't able to fig. out how to access its methods.
My commom Helper class code:
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Here is my main program code where I've included the Helper class & trying to access it's getUserDetailsFromSharedPreference (). In this case, I'm getting an error like Only static memebers can be accessed in initializers. I also tried to extends Helper class in UserProfile class. But, there also I'm getting a different kind of errors. Can't able to identify how to do this thing.
import 'package:flutter/material.dart';
import 'helper.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
var userData = helper.getUserDetailsFromSharedPreference();
}
#Günter Zöchbauer I've made my Helper.dart file like this as you've suggested -
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Now, in my tryint to access that getUserDetailsFromSharedPreference() method I'm getting the same error Only static memebers can be accessed in initializers .
You could ensure a singleton instance of the class using a public factory constructor with a private regular constructor:
class Helper {
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
...
}
If you call new Helper(), you'll always get the same instance.
You need to import the file that contains class Helper {} everywhere where you want to use it.
??= means new Helper._() is only executed when _instance is null and if it is executed the result will be assigned to _instance before it is returned to the caller.
update
getUserDetailsFromSharedPreference is async and can therefore not be used in the way you use it, at least it will not lead to the expected result. getUserDetailsFromSharedPreference returns a Future that provides the result when the Future completes.
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
Future<Map> _userData; // this with ??= of the next line is to prevent `getUserDetailsFromSharedPreference` to be called more than once
Future<Map> get userData => _userData ??= helper.getUserDetailsFromSharedPreference();
}
If you need to access userData you need to mark the method where you do with async and use await to get the result.
foo() async {
var ud = await userData;
print(ud);
}
To access other class method you can simply put static on the method.
class Helper {
static printing(String someText){
print(someText);
}
}
void main() {
Helper.printing('Hello World!');
}
I think this question is more related to accessing one class data in another class. So I explained on the basis of my understanding of the question but if I'm not correct about it.
but if you want to access data of class A, not directly but through class B.
so first you have to make an object of A in class B but remember one thing you would have to make the object static in order to get access to the data of class A within Class B
If you still are confused about all this, I made a solution video.
Check it out: https://youtu.be/shK7ZraruCI
My question is really a follow up question to
RabbitMQ Integration Test and Threading
There it states to wrap "your listeners" and pass in a CountDownLatch and eventually all the threads will merge. This answer works if we were manually creating and injecting the message listener but for #RabbitListener annotations... i'm not sure how to pass in a CountDownLatch. The framework is auto magically creating the message listener behind the scenes.
Are there any other approaches?
With the help of #Gary Russell I was able to get an answer and used the following solution.
Conclusion: I must admit i'm indifferent about this solution (feels like a hack) but this is the only thing I could get to work and once you get over the initial one time setup and actually understand the 'work flow' it is not so painful. Basically comes down to defining ( 2 ) #Beans and adding them to your Integration Test config.
Example solution posted below with explanations. Please feel free to suggest improvements to this solution.
1. Define a ProxyListenerBPP that during spring initialization will listen for a specified clazz (i.e our test class that contains #RabbitListener) and
inject our custom CountDownLatchListenerInterceptor advice defined in the next step.
import org.aopalliance.aop.Advice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
/**
* Implements BeanPostProcessor bean... during spring initialization we will
* listen for a specified clazz
* (i.e our #RabbitListener annotated class) and
* inject our custom CountDownLatchListenerInterceptor advice
* #author sjacobs
*
*/
public class ProxyListenerBPP implements BeanPostProcessor, BeanFactoryAware, Ordered, PriorityOrdered{
private BeanFactory beanFactory;
private Class<?> clazz;
public static final String ADVICE_BEAN_NAME = "wasCalled";
public ProxyListenerBPP(Class<?> clazz) {
this.clazz = clazz;
}
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (clazz.isAssignableFrom(bean.getClass())) {
ProxyFactoryBean pfb = new ProxyFactoryBean();
pfb.setProxyTargetClass(true); // CGLIB, false for JDK proxy (interface needed)
pfb.setTarget(bean);
pfb.addAdvice(this.beanFactory.getBean(ADVICE_BEAN_NAME, Advice.class));
return pfb.getObject();
}
else {
return bean;
}
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1000; // Just before #RabbitListener post processor
}
2. Create the MethodInterceptor advice impl that will hold the reference to the CountDownLatch. The CountDownLatch needs to be referenced in both in the Integration test thread and inside the async worker thread in the #RabbitListener. So we can later release back to the Integration Test thread as soon as the #RabbitListener async thread has completed execution. No need for polling.
import java.util.concurrent.CountDownLatch;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* AOP MethodInterceptor that maps a <b>Single</b> CountDownLatch to one method and invokes
* CountDownLatch.countDown() after the method has completed execution. The motivation behind this
* is for integration testing purposes of Spring RabbitMq Async Worker threads to be able to merge
* the Integration Test thread after an Async 'worker' thread completed its task.
* #author sjacobs
*
*/
public class CountDownLatchListenerInterceptor implements MethodInterceptor {
private CountDownLatch countDownLatch = new CountDownLatch(1);
private final String methodNameToInvokeCDL ;
public CountDownLatchListenerInterceptor(String methodName) {
this.methodNameToInvokeCDL = methodName;
}
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
if (this.methodNameToInvokeCDL.equals(methodName) ) {
//invoke async work
Object result = invocation.proceed();
//returns us back to the 'awaiting' thread inside the integration test
this.countDownLatch.countDown();
//"reset" CountDownLatch for next #Test (if testing for more async worker)
this.countDownLatch = new CountDownLatch(1);
return result;
} else
return invocation.proceed();
}
public CountDownLatch getCountDownLatch() {
return countDownLatch;
}
}
3. Next add to your Integration Test Config the following #Bean(s)
public class SomeClassThatHasRabbitListenerAnnotationsITConfig extends BaseIntegrationTestConfig {
// pass into the constructor the test Clazz that contains the #RabbitListener annotation into the constructor
#Bean
public static ProxyListenerBPP listenerProxier() { // note static
return new ProxyListenerBPP(SomeClassThatHasRabbitListenerAnnotations.class);
}
// pass the method name that will be invoked by the async thread in SomeClassThatHasRabbitListenerAnnotations.Class
// I.E the method name annotated with #RabbitListener or #RabbitHandler
// in our example 'listen' is the method name inside SomeClassThatHasRabbitListenerAnnotations.Class
#Bean(name=ProxyListenerBPP.ADVICE_BEAN_NAME)
public static Advice wasCalled() {
String methodName = "listen";
return new CountDownLatchListenerInterceptor( methodName );
}
// this is the #RabbitListener bean we are testing
#Bean
public SomeClassThatHasRabbitListenerAnnotations rabbitListener() {
return new SomeClassThatHasRabbitListenerAnnotations();
}
}
4. Finally, in the integration #Test call... after sending a message via rabbitTemplate to trigger the async thread... now call the CountDownLatch#await(...) method obtained from the interceptor and make sure to pass in a TimeUnit args so it can timeout in case of long running process or something goes wrong. Once the async the Integration Test thread is notified (awakened) and now we can finally begin to actually test/validate/verify the results of the async work.
#ContextConfiguration(classes={ SomeClassThatHasRabbitListenerAnnotationsITConfig.class } )
public class SomeClassThatHasRabbitListenerAnnotationsIT extends BaseIntegrationTest{
#Inject
private CountDownLatchListenerInterceptor interceptor;
#Inject
private RabbitTemplate rabbitTemplate;
#Test
public void shouldReturnBackAfterAsyncThreadIsFinished() throws Exception {
MyObject payload = new MyObject();
rabbitTemplate.convertAndSend("some.defined.work.queue", payload);
CountDownLatch cdl = interceptor.getCountDownLatch();
// wait for async thread to finish
cdl.await(10, TimeUnit.SECONDS); // IMPORTANT: set timeout args.
//Begin the actual testing of the results of the async work
// check the database?
// download a msg from another queue?
// verify email was sent...
// etc...
}
It's a bit more tricky with #RabbitListener but the simplest way is to advise the listener.
With the custom listener container factory just have your test case add the advice to the factory.
The advice would be a MethodInterceptor; the invocation will have 2 arguments; the channel and the (unconverted) Message. The advice has to be injected before the container(s) are created.
Alternatively, get a reference to the container using the registry and add the advice later (but you'll have to call initialize() to force the new advice to be applied).
An alternative would be a simple BeanPostProcessor to proxy your listener class before it is injected into the container. That way, you will see the method argumen(s) after any conversion; you will also be able to verify any result returned by the listener (for request/reply scenarios).
If you are not familiar with these techniques, I can try to find some time to spin up a quick example for you.
EDIT
I issued a pull request to add an example to EnableRabbitIntegrationTests. This adds a listener bean with 2 annotated listener methods, a BeanPostProcessor that proxies the listener bean before it is injected into a listener container. An Advice is added to the proxy which counts latches down when the expected messages are received.
I'm new to IoC/DI frameworks. The first one I am trying is the Ninject framework. It seems straightforward, but I can't get my console application to run. I keep getting an ActivationException stating:
"Error activating MainWindow: the StandardProvider returned an instance of type FutureRealisticDateManager, which is not compatible with the requested service.
Using default binding from MainWindow to FutureRealisticDateManager (via StandardProvider)
declared by DateBindingModule.Load()
Activation path:
1) active request for MainWindow
from Program.Main()
using default binding from MainWindow to FutureRealisticDateManager (via StandardProvider)
declared by DateBindingModule.Load()"
My code is very simple. Here is my Program file...
class Program
{
static void Main(string[] args)
{
IKernel k = new StandardKernel(new DateBindingModule());
MainWindow mw = k.Get<MainWindow>();
mw.Show();
Console.ReadLine();
}
}
Here is my ninject binding module...
public class DateBindingModule : StandardModule
{
public override void Load()
{
Bind<MainWindow>().To<FutureRealisticDateManager>();
}
}
Here is the class that receives the injected class...
class MainWindow
{
private IRealisticDateManager _d;
[Inject]
public MainWindow(IRealisticDateManager dateManager)
{
_d = dateManager;
}
public void Show()
{
Console.WriteLine("*** MainWindow [" + _d.GetRealisticDate().ToString() + "] ");
}
}
And here is the actual class being injected...
public class FutureRealisticDateManager : TestNinject.IRealisticDateManager
{
public DateTime GetRealisticDate()
{
return DateTime.Now.AddDays(15);
}
}
And finally the simple interface that the injected class should implement...
public interface IRealisticDateManager
{
DateTime GetRealisticDate();
}
Can anybody see what I am doing wrong?
It fails because your binding is not right:
Bind<MainWindow>().To<FutureRealisticDateManager>();
FutureRealisticDataManager does not inherit from MainWindow.
Instead you should write:
Bind<IRealisticDateManager>().To<FutureRealisticDateManager>();
This tells Ninject to return FutureRealisicDateManager whenever IRealisicDateManager is requested.
This way when you call k.Get<MainWindow>() kernel will inject FutureRealisticDateManager to the constructor.