@Inject not working in AttributeConverter

2019-02-22 19:59发布

I have a simple AttributeConverter implementation in which I try to inject an object which have to provide the conversion logic, but @Inject seem not to work for this case. The converter class looks like this:

@Converter(autoApply=false)
public class String2ByteArrayConverter implements AttributeConverter<String, byte[]>
{
    @Inject
    private Crypto crypto;

    @Override
    public byte[] convertToDatabaseColumn(String usrReadable) 
    {
        return crypto.pg_encrypt(usrReadable);
    }

    @Override
    public String convertToEntityAttribute(byte[] dbType)
    {
        return crypto.pg_decrypt(dbType);
    }
}

When the @Converter is triggered it throws an NullPointerException because the property crypto is not being initialized from the container. Why is that?

I'm using Glassfish 4 and in all other cases @Inject works just fine.

Is it not possible to use CDI on converters?

Any help will be appreciated :)


The accent of my question is more the AttributeConverter part. I understand that for the CDI to work a bean must meet the conditions described here http://docs.oracle.com/javaee/6/tutorial/doc/gjfzi.html. I also have tried to force the CDI to work by implementing the following constructor:

@Inject
public String2ByteArrayConverter(Crypto crypto) 
{
    this.crypto = crypto;
}

And now I got the following exception which doesn't give me any clue:

2015-07-23T01:03:24.835+0200|Severe: Exception during life cycle processing
org.glassfish.deployment.common.DeploymentException: Exception [EclipseLink-28019] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Deployment of PersistenceUnit [PU_VMA] failed. Close all factories for this PersistenceUnit.
Internal Exception: Exception [EclipseLink-7172] (Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.ValidationException
Exception Description: Error encountered when instantiating the class [class model.converter.String2ByteArrayConverter].
Internal Exception: java.lang.InstantiationException: model.converter.String2ByteArrayConverter
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.createDeployFailedPersistenceException(EntityManagerSetupImpl.java:820)
at org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl.deploy(EntityManagerSetupImpl.java:760)
...

I even tried using @Producer or @Decorator in order to have the CDI working on that place, but I still think there is something specific with the AttributeConverter which doesn't allow CDI. So problem not solved yet.

4条回答
疯言疯语
2楼-- · 2019-02-22 20:30

You're trying to combine two different worlds, as CDI doesn't know about JPA Stuff and vice-versa. (One annotation parser of course doesn't know about the other) What you CAN do, is this:

/**
 * @author Jakob Galbavy <code>jg@chex.at</code>
 */
@Converter
@Singleton
@Startup
public class UserConverter implements AttributeConverter<User, Long> {
    @Inject
    private UserRepository userRepository;
    private static UserRepository staticUserRepository;

    @PostConstruct
    public void init() {
        staticUserRepository = this.userRepository;
    }

    @Override
    public Long convertToDatabaseColumn(User attribute) {
        if (null == attribute) {
            return null;
        }
        return attribute.getId();
    }

    @Override
    public User convertToEntityAttribute(Long dbData) {
        if (null == dbData) {
            return null;
        }
        return staticUserRepository.findById(dbData);
    }
}

This way, you would create a Singleton EJB, that is created on boot of the container, setting the static class attribute in the PostConstruct phase. You then just use the static Repository instead of the injected field (which will still be NULL, when used as a JPA Converter).

查看更多
时光不老,我们不散
3楼-- · 2019-02-22 20:34

For reference, JPA 2.2 will allow CDI to be used with AttributeConverter, and some vendors already support this (EclipseLink, DataNucleus JPA are the ones I know of that do it).

查看更多
女痞
4楼-- · 2019-02-22 20:35

Well, CDI still doesn't work for AttributeConverter, which would be the most elegant solution, but I have found a satisfying workaround. The workaround is using @FacesConverter. Unfortunately per default CDI doesn't work in faces converters and validators either, but thanks to the Apache MyFaces CODI API you can make it work unsing the @Advaced annotation :) So I came up with an implementation like this:

@Advanced
@FacesConverter("cryptoConverter")
public class CryptoJSFConverter implements Converter
{
    private CryptoController crypto = new CryptoController();

    @Inject
    PatientController ptCtrl;

    public Object getAsObject(FacesContext fc, UIComponent uic, String value) 
    {
        if(value != null)
            return crypto.pg_encrypt(value, ptCtrl.getSecretKey());
        else
            return null;
     }


    public String getAsString(FacesContext fc, UIComponent uic, Object object) 
    {
        String res = crypto.pg_decrypt((byte[]) object, ptCtrl.getSecretKey());
        return res;
    }   
}

The injected managed bean has to be explicitly annotated with @Named and some scope definition. A declaration in faces-config.xml doesn't work! In my solution it looks like this:

@Named
@SessionScoped
public class PatientController extends PersistanceManager
{
   ...
}

Now one has a context information in the converter. In my case it is session/user specific cryptography configuration.

Of course in such a solution it is very likely that a custom @FacesValidator is also needed, but thanks to CODI one have the possibility for using CDI here also (analog to converter).

查看更多
Root(大扎)
5楼-- · 2019-02-22 20:49

Unfortunately you can't inject CDI beans into a JPA converter, however in CDI 1.1 you can inject your Crypto programmatically :

Crypto crypto  = javax.enterprise.inject.spi.CDI.current().select(Crypto.class).get()
查看更多
登录 后发表回答