Search code examples
javaspringautowiredabstractfinal

Spring @Autowired not working with final method on abstract super class


I have a abstract super class with autowired fields and a final method. Additionally there is a concrete sub class with a method which calls the final method on the super class.

Abstract super class:

public abstract class AbstractSuper {
    @Autowired
    protected AuthenticationService authenticationService;

    @Autowired
    protected JobRepository jobRepository;

    public final void startOnSuper() {
        test();
    }

    private void test() {
        assert authenticationService != null;
        assert jobRepository != null;
    }
}

Concrete implementation:

@Component
@RequiredArgsConstructor
public class ConcreteImpl extends AbstractSuper {
    private final PersonService personService;

    public void startOnImpl() {
        super.startOnSuper();
    }
}

JUnit:

@Autowired
private ConcreteImpl concrete;

@Test
public void startOnImpl() {
    concrete.startOnImpl();
}

@Test
public void startOnSuper() {
    concrete.startOnSuper();
}

Why does the startOnImpl test work and the startOnSuper test fail? Why does it matter whether I call the method from the sub class or from outside the class? Shouldn't the state of the object be the same?


Solution

  • The startOnSuper method fails precisely because it has the final modifier, which is not present in the startOnImpl method. The calls concrete.startOnImpl(); and concrete.startOnSuper(); within your test class do not directly call the class instance, but rather a dynamically created proxy. The call super.startOnSuper();, inside ConcreteImpl#startOnImpl, does not go through the proxy (in fact, it already passed before entering this method) but instead invokes the method of the instance itself, so the error does not occurs.

    The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy. This means that method calls on that object reference are calls on the proxy. As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object (the SimplePojo reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. Section 5.8.1

    In this case, a proxy is being created based on the class (class-based proxy), and this implies some rules for how it works, including not applying the final modify to public or protected methods (which will be accessed through the proxy).

    This proxy is a wrapper that is dynamically created at runtime and needs to override all accessible methods so that they are not called directly, but always in a control pattern of the proxy itself. If the method is final it cannot be overridden, which will result in unexpected behavior.

    The easy solution:

    Remove the final modifier.

    But this will also depend on the design you are using in the project, and I don't remember any other practical alternative for this case.

    You can read more about it at the following links: