How to re-inject a transient @ManagedProperty at d

2019-04-07 03:06发布

I'm using Spring and JSF 2 to create a web application. The business objects are kept in the Spring container, and I inject them in the Managed Beans using the @ManagedProperty, like this:

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    @Getter @Setter
    @ManagedProperty("#{someService}")
    private SomeService someService;
    // ...

The problem is, I keep getting a NotSerializableException for a class from Spring (ServiceLocatorFactoryBean) that it's being used by the SomeService bean.

If I make it transient, how could I do the re-injection of it after deserializing?

Or, what would be other ways to solve this problem?

I've been reading several other questions similar here, but couldn't find any that dealt exactly with this problem.

4条回答
Viruses.
2楼-- · 2019-04-07 03:35

Try @Scope(value = BeanDefinition.SCOPE_SINGLETON, proxyMode = ScopedProxyMode.INTERFACES) on your Spring @Service. This should inject a serializable proxy object into your managed bean that will relocate the service upon access after deserialization.

查看更多
你好瞎i
3楼-- · 2019-04-07 03:40

Well keep this in mind from the Spring manual ( link to spring):

Constructor-based or setter-based DI?

Since you can mix both, Constructor- and Setter-based DI, it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies. Note that the use of a @Required annotation on a setter can be used to make setters required dependencies.

The Spring team generally advocates setter injection, because large numbers of constructor arguments can get unwieldy, especially when properties are optional. Setter methods also make objects of that class amenable to reconfiguration or re-injection later. Management through JMX MBeans is a compelling use case.

Some purists favor constructor-based injection. Supplying all object dependencies means that the object is always returned to client (calling) code in a totally initialized state. The disadvantage is that the object becomes less amenable to reconfiguration and re-injection.

Use the DI that makes the most sense for a particular class. Sometimes, when dealing with third-party classes to which you do not have the source, the choice is made for you. A legacy class may not expose any setter methods, and so constructor injection is the only available DI.

查看更多
迷人小祖宗
4楼-- · 2019-04-07 03:45

For those to follow - I had a similar problem with an injected ResourceBundle. Using part of BalusC's answer, I did the following:

@ManagedProperty(value="#{myBundle}")
private transient ResourceBundle myBundle;

private Object readResolve() {
    myBundle = FacesContext.getCurrentInstance().getApplication()
        .evaluateExpressionGet(FacesContext.getCurrentInstance(), "#{myBundle}",
        ResourceBundle.class);
    return this;
}

This way, the EL is evaluated only when the managed bean is deserialized.

查看更多
地球回转人心会变
5楼-- · 2019-04-07 03:54

Instead of injecting the Spring beans via EL in a @ManagedProperty annotation (executed on the ManagedBean initialization), obtain the beans evaluating the EL at runtime.

With this approach, this is what the JSF beans should look like:

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    private static SomeService someService() {
        return SpringJSFUtil.getBean("someService");
    }
    // ...

And the utility class SpringJSFUtil.java that gets the bean via EL:

import javax.faces.context.FacesContext;

public class SpringJSFUtil {

    public static <T> T getBean(String beanName) {
        if (beanName == null) {
            return null;
        }
        return getValue("#{" + beanName + "}");
    }

    @SuppressWarnings("unchecked")
    private static <T> T getValue(String expression) {
        FacesContext context = FacesContext.getCurrentInstance();
        return (T) context.getApplication().evaluateExpressionGet(context,
                expression, Object.class);
    }
}

This eliminates the Spring bean property (at the cost of doing a few more EL evaluations), thus avoiding all the serialization issues of having the property in first place.

The same approach, using OmniFaces:

In my actual code, I use the evaluateExpressionGet(String expression) method of an utility class available from OmniFaces. So, for those of you who use it too, this is what my code really look like:

import static org.omnifaces.util.Faces.evaluateExpressionGet;

@ManagedBean
@ViewScoped
public class SomeMB implements Serializable {
    private static final long serialVersionUID = 1L;

    private static SomeService someService() {
        return evaluateExpressionGet("#{someService}");
    }
    // ...

Notice that here the method gets the full EL ("#{expression}"), not just the Spring bean name (or you would get a ClassCastException).

查看更多
登录 后发表回答