2

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 Answers1

0

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 ?

R.G
  • 6,436
  • 3
  • 19
  • 28
  • Thank you for your answer, but it is so case-specific, I mean I need this proxy for all the classes that implemented CompensateAware, one class may have many dependencies not only one dependency(like in the question), in short, you answer is not generic enough. – Mehrdad HosseinNejad Yami Mar 14 '20 at 15:51
  • Why transaction proxy has been disabled after my proxy? I mean, is it possible to make a nested proxy? Like making a proxy of the proxy object (like my question, proxy of transactional proxy) – Mehrdad HosseinNejad Yami Mar 14 '20 at 15:52
  • Enhancer creates a new object which is not managed by spring , which explains why there is no txn support and no dependency injection . The best approach in this case is to go with Spring AOP. Please raise a new question for your need for a proxy of proxy object , then again , AOP is the correct way for the logging unless you have a very specific reason for not using the same – R.G Mar 14 '20 at 16:55
  • I used AOP, but it didn't work correctly, I wanted to aspect on transactional methods, and log possible transaction exceptions(like unique constraint), but it was like that AOP opened before transaction proxy, so if any transaction exception occurs My AOP can not catch and log it. – Mehrdad HosseinNejad Yami Mar 14 '20 at 17:13
  • Consequently, for this disruption of priorities, I prefer to use the proxy mechanism. (Of course, AOP is also based on the proxy mechanism) – Mehrdad HosseinNejad Yami Mar 14 '20 at 17:15
  • Not sure why AOP will not work in this case . Probably it is better you create a new question with your code and issue . Enhancer is definitely not the route for the logging here – R.G Mar 14 '20 at 17:21