Null dependencies in the proxy class

Viewed 322

Preface: I want to proxy some classes and intercept their methods (using cglib and BeanPostProcessor). These classes are spring beans (@Service) and normally they have some dependency like a repository.

Problem: When I create a proxy for a bean, the dependencies of the proxy class are null (they are not injected into proxy class).

what should I do to the dependency of proxy class injected properly?


Scenario: I want to create a proxy for service classes that implemented a CompensateAware interface and then I want to print some log before invoking their methods.

My codes are as follows

Service class (Original Class)

@Service
public class RequestTrackingService implements CompensateAware {

    @Autowired
    public RequestTrackingRepository repository;

    @Transactional
    public void startTracking() {
        RequestTrackingEntity requestTrackingEntity = new RequestTrackingEntity();
        requestTrackingEntity.setStatus(TransactionStatus.RECEIVED);
        requestTrackingEntity.setId(ServiceContext.getTrackingNo());
        repository.save(requestTrackingEntity);
    }

}

Create Proxt Classes using BeanPostProcessor and cglib

@Component
public class ServiceProxy implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean instanceof CompensateAware) {
            return beanProxy(bean);
        } else return bean;
    }

    //Create proxy using cglib
    private Object beanProxy(Object bean) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(bean.getClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
              System.out.println("SOME LOG BEFORE METHOD");
             return methodProxy.invokeSuper(o, objects);
        });
        return enhancer.create();
    }
}

Call service method (startTracking)

@Service
public class TestClass {

    @Autowired
    private RequestTrackingService service;

    public void test() {
        //I get NullPointerException because of null dependency (repository) in service 
        service.startTracking();
    }

}

In short:

When i call startTracking method (method of proxy class) somewhere in my app, i get NullPointerException, because its dependency(repository) is not injected and it's null`.

1 Answers

Enhancer.create()

Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance. Uses the no-arg constructor of the superclass.

creates a new instance of the SuperClass configured . The new instance is not a spring managed bean and hence dependency injection did not happen. Also note that the transactional behaviour of the startTracking() method will also be lost with the enhancer instance.

To get around the NPE , you will need to set the repository bean programmatically.

I did the following code change to make the code work

@Service
public class RequestTrackingService implements CompensateAware {

    public RequestTrackingRepository repository;

    public RequestTrackingService(RequestTrackingRepository repository) {
        this.repository = repository;
    }

    @Transactional
    public void startTracking() {
        RequestTrackingEntity requestTrackingEntity = new RequestTrackingEntity();
        requestTrackingEntity.setStatus(TransactionStatus.RECEIVED);
        requestTrackingEntity.setId(ServiceContext.getTrackingNo());
        repository.proxyRepoTestMethod();
    }


    public RequestTrackingRepository getRepository() {
        return repository;
    }
}

and

   //Create proxy using cglib
    private Object beanProxy(Object bean) {
        Enhancer enhancer = new Enhancer();
        // Set RequestTrackingService as the super class 
        enhancer.setSuperclass(((Advised)bean).getTargetClass());
        enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
              System.out.println("SOME LOG BEFORE METHOD");
             return methodProxy.invokeSuper(o, objects);
        });
        RequestTrackingService serviceBean = (RequestTrackingService)enhancer.create(new Class[] {RequestTrackingRepository.class}, new Object[] {((RequestTrackingService)bean).getRepository()});
        return serviceBean;
    }

Hope this helps

Update 1 : This may not be a complete answer . I am not able to get the line "SOME LOG BEFORE METHOD" printed with this code change.

Update 2 : Turns out that bean.getClass() returns the class of the already enhanced (proxy created by spring) object. This somehow prevents the logging code in the proxyMethod delegation code.

example class retruned:

class com.xxx.xxx.proxy.RequestTrackingService$$EnhancerBySpringCGLIB$$40bcb718

Modifying the code to set the actual target class fixes the logging issue as well.

enhancer.setSuperclass(((Advised)bean).getTargetClass());

A final question though , any specific reason you are not trying Spring AOP for this usecase ?

Related