0

I'm trying to create my own lazy load implementation using CGLib, but i've faced some strange behavior that I cannot explain.

Here is what i'm doing.
Proxy instance is being created like follows:

public static <T> T newInstance(Long sourceId, SourceMapper<T> sourceMapper) {

    Class<?> proxyTargetType = sourceMapper.getType();
    //mapper will use provided sourceId in order to load real object from the DB
    Function<Long, T> mapper = sourceMapper.getMapper();

    return (T) Enhancer.create(proxyTargetType,
                               new DynamicProxy<>(sourceId, mapper));
}

Here is the usage of the code above:

Order order = new Order();
     try {
          //heavy object is being proxied
          long customerTariffId = rs.getLong("customer_tariff_id");
          order.setCustomerTariff(DynamicProxy
                          .newInstance(customerTariffId, CUSTOMER_TARIFF_MAPPER)); 
        }

Heavy object should be loaded only if any of its methods gets invoked:

public Object intercept(Object obj, Method method, Object[] args,
                        MethodProxy methodProxy) throws Throwable {
    T source = this.getSource(); // loads real object using sourceId and mapper
    if(source == null) return null;
    return method.invoke(source, args);
}

It works perfectly if this.getSource() loads some object.

But here what i'm getting if we assume, that order.getCustomerTariff() should return null (this.getSource() will return null)

LOG.debug("{}", order.getCustomerTariff());          //null    (1)
LOG.debug("{}", order.getCustomerTariff() != null);  //true    (2)

I assume, that for some reason toString() gets invoked at line (2), so i'm getting String null instead of literal null. That's why it is not equal to a literal null in the comparison clause.
How do you think, is there any way to return a regular null at line (2) and receive a correct value of false during that check?

EDIT
Class being proxied looks similar to this:

public class CustomerTariff extends DomainEntity {

    private Customer customer;
    //some other similar fields
    private Tariff tariff;

    public CustomerTariff() {
    }

    public CustomerTariff(Customer customer
                          Tariff tariff) {
        this.customer = customer;
        this.tariff = tariff;
    }

    public CustomerTariff(Long id, Customer customer,
                          Tariff tariff) {
        super(id);
        this.customer = customer;
        this.tariff = tariff;
    } 
    //getters and setters

    @Override
    public String toString() {
    return "CustomerTariff{" +
            "customer=" + customer +
            ", tariff=" + tariff +
            "} " + super.toString();
    }
}

public abstract class DomainEntity {

    private Long id;

    public DomainEntity() {}

    public DomainEntity(Long id) {
       this.id = id;
    }

    @Override
    public String toString() {
       return "DomainEntity{" +
               "id=" + id +
                '}';
       }
    }
darien
  • 1
  • 1
  • What you do seems correct. Did you inspect the type of the returned value? Did you set a break point in your interceptor to check the correct value being returned? – Rafael Winterhalter May 21 '17 at 20:45
  • @RafaelWinterhalter From the debugging session i understood, that `Enhancer.create(...)` generates the following value `obj = {CustomerTariff$$EnhancerByCGLIB$$d66ba677} "null"` which is being set it `customerTariff` field. This value is being compared with `null` at line(2). I don't understand what that quoted null mean, but the thing is that `"null" != null` yields `true` – darien May 22 '17 at 06:17
  • I assume that there is some constructor which defines a value that becomes part of the `toString` representation. I assume that you call the constructor but do not initialize some field that is being read. Could you provide the class being proxied? – Rafael Winterhalter May 22 '17 at 07:06
  • @RafaelWinterhalter Yes, i can provide the class. I have edited my question – darien May 22 '17 at 09:06

1 Answers1

0

I assume that you are intercepting your toString method from your interceptor and you do not get the interception chain you expect. Specify a method filter that only hits the methods you want to intercept and you should get the expected result.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192