3

I need a service (singleton fits) with some internal fields, like a list of pending threads (yes everything is written to be thread safe) the problem is that if I @autowire this bean, fields appear to be empty. Debugging I see that the proxy correctly binds to the instance (fields CGLIB$CALLBACK_X are correctly linked to the populated bean) with populated fields, but the fields it offers are empty.

The following lines of codes give a general idea of what i'm talking about.

@Service
public class myService{

   @Autowired
   private Monitor monitor;

   public List getSomething(){
       return monitor.getList();
   }
}


@Service
public class myStatefulService{

   //This field will be populated for sure by someone before getSomething() is called
   private List list;

   public synchronized List getSomething(){
       return this.list;
   }

   //Called by other services that self inject this bean 
   public synchronized void addToList(Object o){
      this.list.add(o);
   }
}

Debugging the variable monitor during the getList call I get

monitor => instance of correct class
 fields:
   CGLIB$BOUND => true
   CGLIB$CALLBACK_0.advised => proxyFactory (correct)
   CGLIB$CALLBACK_1.target (reference to the correct instance of myStatefulService class)
        fields:
          list => [.........] (correctly populated)
   CGLIB$CALLBACK_2 ..... 
   ......
   ......
   ......
   list => [] (the list that would be populated is empty instead)
slava
  • 1,901
  • 6
  • 28
  • 32
Gnappuraz
  • 250
  • 3
  • 12

3 Answers3

16

Are you curious or you have some real issue? Nevertheless here is an explanation.

When using CGLIB to proxy classes Spring will create a subclass called something like myService$EnhancerByCGLIB. This enhanced class will override some if not all of your business methods to apply cross-cutting concerns around your actual code.

Here comes the real surprise. This extra subclass does not call super methods of the base class. Instead it creates second instance of myService and delegates to it. This means you have two objects now: your real object and CGLIB enhanced object pointing to (wrapping) it.

The enhanced class is just a dummy proxy. It still has the same fields as your base class (inherited from it) but they are not used. When you call addToList() on myService$EnhancerByCGLIB object it will first apply some AOP logic, call addToList() of myService (which it wraps) and apply remaining AOP logic on return. The myService$EnhancerByCGLIB.list field is never touched.

Why can't Spring use the same class and delegate via super? I guess for simplicity: first create "raw" bean and then apply AOP proxying during post-processing.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
  • That mention of `super` saved my day! Was fooling around for hours trying to figure out why my base class is being called instead of the enhanced version - until I realized that the invoker was calling upon a reference to `this` passed back from within the base class itself - which obviously wasn't going to work. – Janaka Bandara Jul 23 '20 at 13:34
4

"This field will be populated for sure by someone before getSomething() is called"

By someone? No, the Spring bean factory. If you don't configure it, nothing will be populated.

Not every bean needs to be under Spring's control. It sounds like you want to have a List that clients can add and remove items to in a thread-safe way. If that's true, remove the @Autowired annotation, create a new List, and expose methods to add and remove.

I'd recommend a List from the new concurrent collections.

duffymo
  • 305,152
  • 44
  • 369
  • 561
  • Sorry, the @autowired on the list is a typing error.... everything is under Spring control's, "someone" is an other service that responds to client actions... so client add elements to the list using other services. The service with the list contains logic and do work periodically on the list, so i need to be a singleton injectable stateful bean =) – Gnappuraz Jul 20 '12 at 14:22
  • And yes i offer methods to add elements to the list but when the list is read it's empty also if the instance pointed by the proxy has it populated correctly. – Gnappuraz Jul 20 '12 at 14:29
  • Why do you need a proxy? I would say that this is an example of an object that need not be under Spring's control. Just instantiate a new one and get on with it. – duffymo Jul 20 '12 at 16:50
2

CGLIB will proxy protected getters.

So you can have:

@Autowired
private Monitor monitor;

protected Monitor getMonitor() { return monitor; }

public List getSomething(){
    return getMonitor().getList();
}

getMonitor() will be proxied to call getMonitor() on the other instance which has monitor injected.

Anthony Hayward
  • 2,164
  • 21
  • 17