8

I have a class called Menu, annnotated as @Entity where i need to use a method inside a class called GestoreMessaggi

....    
@Component
@Entity
@Table(name="menu")
public class Menu implements Serializable{

@Autowired
@Transient // because i dont' save this field on the DB
private GestoreMessaggi gestoreMessaggi;
.....
public void setCurrentLanguage(){

   /* I got the error both if use gestoreMessaggi
   this way and if I use the autowired istance of GestoreMessaggi*/
   GestoreMessaggi gestoreMessaggi = new GestoreMessaggi();  
   gestoreMessaggi.gest();        
}
.....

This is the relevant code of the class GestoreMessaggi

 @Component
    public class GestoreMessaggi {    
        @Autowired 
        private ReloadableResourceBundleMessageSource messageSource;

        public void gest(){
        messageSource.doSomething() <--- here messageSource is null
        }
  }

When, I call gestoreMessaggi.gest(); from Menu class, I got an error because messageSource is null. The gestoreMessaggi istance is NOT null, is null just messageSource

IMPORTANT: I get null on messageSource only when I call GestoreMessaggi from classes annotated as @Entity.

In ds-servlet.xml I tell Spring to scan the pakages that contain Menu and GestoreMessaggi classes:

//Menu package 
<context:component-scan base-package="com.springgestioneerrori.model"/>
//Gestore messaggi package
<context:component-scan base-package="com.springgestioneerrori.internazionalizzazione"/>   

Thank you

MDP
  • 4,177
  • 21
  • 63
  • 119
  • Please include your spring configuration. Are you sure that the `Menu` class is managed by Spring? – meskobalazs Feb 06 '15 at 11:52
  • 3
    Spring will only inject dependencies into objects it manages itself. `@Entity` annotated classes aren't managed by spring (regardless of the `@Component` annotation). – M. Deinum Feb 06 '15 at 12:10
  • 1
    It's preferable to use `@Autowired` on a constructor and make the fields `final`. If you do that, then you will most certainly realize why `messageSource` is null. – a better oliver Feb 06 '15 at 12:33
  • What should I do to tell Spring to 'manage' my objects? – MDP Feb 06 '15 at 13:02
  • @Matteo So, to clarify... You have the `Menu` class, which is annotated with `@Entity` and `@Component`, and in a method of the `Menu` class you have a `GestoreMessaggi` instance, which in turn has a `ReloadableResourceBundleMessageSource` instance that you're expecting to be autowired by Spring. Is this correct? How do you get an instance of `GestoreMessaggi`? Are you autowiring it in the `Menu` class? Or are you creating it with `new GestoreMessaggi()` inside the method?. – fps Feb 06 '15 at 13:03
  • Look at this answer: http://stackoverflow.com/a/19896871/3963330 – Tobías Feb 06 '15 at 13:03
  • @Magnamag I edited my post, I hope is clearer. Anyway, I got the same problem both autowiring GestoreMessaggi in the Menu class and making new GestoreMessaggi() – MDP Feb 06 '15 at 13:29
  • 1
    IMHO you should not even try to resolve this dependency problem. You're putting too much logic into your Entity which preferable should be a simple POJO. Figuring out the language (or translation or whatever you do there) should not be done inside your entity; that value should be given to your entity (e.g. by your service, Business Object, ... ). – Stijn Geukens Feb 06 '15 at 13:48
  • Actually I was adding setCurrentLanguage(), and other methods, to my Entity class right now. So far, I had setCurrentLanguage() and other methods that provide functions for Menu Objects in another class, separated from Menu.class So, should I keep my original configuration and not add that and other methods inside the @Entity class? – MDP Feb 06 '15 at 13:57
  • Yes, your entity should not care where this information comes from or how it is constructed. Keys concepts here are separation of concern and Domain Driven Design; check it out :-). – Stijn Geukens Feb 06 '15 at 14:05

2 Answers2

9

You can follow two approaches:

  1. Try to get an instance of a Spring-managed bean from within a method of your @Entity class
  2. Change the design as suggested by @Stijn Geukens and make your entity a POJO without any logic or dependency injection machanisms

If you go by option 1, you have to explicitly access Spring's context and retrieve an instance of the bean you need:

@Component
public class Spring implements ApplicationContextAware {

    private static final String ERR_MSG = "Spring utility class not initialized";

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;   
    }

    public static <T> T bean(Class<T> clazz) {
        if (context == null) {
            throw new IllegalStateException(ERR_MSG);
        }
        return context.getBean(clazz);
    }

    public static <T> T bean(String name) {
        if (context == null) {
            throw new IllegalStateException(ERR_MSG);
        }
        return (T) context.getBean(name);
    }
}

You need to make Spring scan this class in order for this to work.

Then, inside your @EntityClass, do this:

public void setCurrentLanguage(){
    GestoreMessaggi gestoreMessaggi = Spring.bean(GestoreMessaggi.class);
    gestoreMessaggi.gest();        
}

And that would be all. Note that you wouldn't need to autowire GestoreMessaggi into your @Entity any more. Please also note that this approach is not recommended neither by Spring nor by most of the community, since it couples your domain classes (your @Entity class) to Spring classes.

If you go by option 2, then all you need to do is let Spring resolve the autowiring as usual, but outside of your entity (i.e. in a dao or service), and if your entity needs you to fill it with some message or whatever, just invoke a setter on it. (Then it's up to you to make your @Entitys attribute @Transient or not, depending on your requirements).

fps
  • 33,623
  • 8
  • 55
  • 110
3

Spring context does not manage entities (in general does not manage objects instantiated using new), this is why you cannot autowire beans (that come from Spring's context) in entities.

Best practices suggest to keep only getter and setter in your entities, leaving the business logic to the service layer.

A common approach is Service <-> DAO <-> Entity. Example:

Service layer:

@Service
public interface GestoreMessaggi {
    public void gest();
}

public class GestoreMessaggiImpl implements GestoreMessaggi {

    @Autowired
    private MenuDao menuDao;

    @Override
    public void gest() {
        // 1) retrieve your entity instance with menuDao
        // 2) do stuffs with your entity
        // 3) maybe save your entity using menuDao
    }
}

DAO layer:

If you use Spring Data Jpa:

public interface MenuDao extends JpaRepository<Menu, [menu-id-type]> {}

Else:

public interface MenuDao {
    public Menu findOne([menu-id-type] id);
    public Menu save(Menu menu);
    // other methods for accessing your data
}

@Repository
public class MenuDaoImpl {
    // inject EntityManager or Hibernate SessionFactory
    // implement your DAO interface accessing your entities
}

Finally remember to configure Spring's beans including your @Services and your @Repositorys in your configuration (explicitly or by package scanning).

Using Spring MVC, you should inject (autowire) your @Services in a @Controller class, so the controller can call services methods, which call DAOs methods, which access your data.

Francesco Pitzalis
  • 2,042
  • 1
  • 16
  • 20