How to inject a non-serializable class (like java.util.ResourceBundle) with Weld
Asked Answered
G

2

22

I want to create a Producer that makes it possible to inject a java.util.ResourceBundle into any class in order to get localized Strings easily. My ResourceBundle-Producer looks like this:

public class ResourceBundleProducer {
  @Inject       
  public Locale locale;

  @Inject       
  public FacesContext facesContext;

  @Produces
  public ResourceBundle getResourceBundle() {
    return ResourceBundle.getBundle("/messages", locale )
  }
}

The Injection of Locale and FacesContext works (took the corresponding producers from the Seam 3 Alpha Source). But unfortunately, ResourceBundle is not Serializable and therefore can't be produced in this way. I'm getting the following Error from Weld when trying to access a JSF-page which calls a bean that uses my ResourceBundle:

Caused by: org.jboss.weld.IllegalProductException: WELD-000054 Producers cannot produce non-serializable instances for injection into non-transient fields of passivating beans\\n\\nProducer\: org.jboss.weld.bean-/D:/Program Files (x86)/GlassFish-Tools-Bundle-For-Eclipse-1.2/glassfishv3/glassfish/domains/teachernews/applications/teachernews/-ProducerMethod-services.producers.ResourceBundleProducer.getResourceBundle()\\nInjection Point\: field web.PersonHome.bundle

Are there any ways to get my ResourceBundleResolver to work? Or are there any other mechanisms to get a similar functionality? Thanks in advance!

EDIT:

Okay, i'll spent some of my hardly earned points ;) Will also accept a good workaround for this issue!

I got another example where creating a Producer doesn't work: a FlashProducer. A FacesContext-Flash also cannot be produced because Flash isn't serializable.

Glycine answered 8/6, 2010 at 18:30 Comment(0)
D
27

Well, First of all ResourceBundle is not Serializable. See here. And The message is clear

cannot produce non-serializable instances for injection into non-transient fields of passivating beans

passivating beans ??? I Think web.PersonHome is Either a Stateful Session Bean or a @ConversationScoped bean. Am i right ??? If so you should mark your bundle property as transient

private transient @Inject ResourceBundle bundle;
Dineen answered 17/7, 2010 at 6:55 Comment(9)
@ifischer disclaimer: I do not use Weld yet. I use Seam. So i am not sure whether @Inject annotation can be used along with transientDineen
Congratz, you just earned yourself 50 points ;) Finally a solution! Wonder why it took that long + a Bounty, as it's kind of easy (also wonder why i didn't find out by myself...). Just tested it, also works with CDI/Weld. Now i can finally inject Resourcebundles and Contexts.Glycine
BTW, PersonHome is a @ConversationScoped CDI/Weld-Bean (@Named)Glycine
@ifischer You are right. Because ConversationScoped must be serializable between requests, all of its fields must implements Serializable interface unless you mark it as transient. It explains why you get The error message. The same issue occurs with Stateful Session beans.Dineen
But with the bundle marked as transient, how can you be sure that it is available when you need it? In case of passivation/activation of the bean, the ResourceBundle is gone and access to it would rais an exception.Intravenous
@Intravenous After a second though,, ResourceBundle is never allowed to stay Null as the app server will re-inject it afresh once detected as Null. This is a basic functionality of any container implementing IoCMcguigan
@Sam but CDI-injection is only done once at creation time of the bean. So the "container" (i.e. Weld) will not recheck the values of it's fields after passivation/activation. Access to the field after passivation/activation will result in a NullPointerException.Intravenous
transient fixes the exception but still I'm getting a warning triangle about unsatisfied dependencies. My beans.xml has bean-discovery-mode="all" in both projects (where the @Cached annotation and producer class is and where the @Cached is being @Inject) but still on both sides I'm getting it, like for @Inject private CacheManager manager;. Any ideas?Decathlon
What's the diference between private transient @Inject and @Inject private transient? Using the former made IntelliJ error go away.`Strang
M
5

As per the comments thread in the accepted answer of Arthur. I followed this blog as well as this one to carry out a passivation/activation experiment. The experiment proved MrD comment that the transient property will be NULL upon activation. Thus, to deal with non-serializable member properties of a passivation capable bean (namely, sessionscoped, conversationscoped and stateful session beans), I suggest the following solution:

private ResourceBundle bundle;

@PostConstruct
@PostActivate
public void getResourceBundle() {
    bundle = ResourceBundle.getBundle("/messages", locale );
}

This solution makes sure that the non-serializable property members are reinitialized every time it enters the READY state.

A final issue to address

A final issue to address is the injection of an SLF4j Logger which was non-serializable before slf4j 1.5.3, and I quote:

As of SLF4J version 1.5.3, logger instances survive serialization. Thus, serialization of the host class no longer requires any special action, even when loggers are declared as instance variables.

Thus as long as your slf4j dependency is 1.5.3 or later you can safely inject an SLF4j Logger as follows:

@Produces
@LogbackLogger
public Logger produceLogger(InjectionPoint injectionPoint){
    return LoggerFactory.getLogger(injectionPoint.getMember().getDeclaringClass().getName());
}

Assuming you declared the qualifier:

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface LogbackLogger {
}

Then in a passivation-capable bean, inject as follows:

@Inject
@LogbackLogger
private Logger logger;
Mcguigan answered 4/2, 2014 at 20:54 Comment(5)
Concerning the "producer" for ResourceBundle: if I understand correctly, you're not using a producer for the ResourceBundle any more - loosing the charm of CDI. Instead you get the ResourceBundle manually on PostConstruct and -Activate events (btw: the PostConstruct and -Activate methods MUST NOT return a value). So this is completely the non-CDI solution which could have been used in JEE5 too, isn't it?Intravenous
@Intravenous Absolutely true!Mcguigan
@Intravenous I edited the initialization method as to return void. Thanks for your sharp eye.Mcguigan
I guess the annotation @PostConstruct should be removed so you can still make use of CDISpacious
@Armando, I like your suggestion. I did not try it though... I wonder what MrD will say about it, he was right all along.Mcguigan

© 2022 - 2024 — McMap. All rights reserved.