Issues with Dynamic method calling after migrating to springboot 3

42 views Asked by At

We are currently in the process of migrating our services from Spring Boot 2.x to Spring Boot 3.2.1 and java 21 version, The following code was functioning well in Spring Boot 2.x but encountered problems after the migration.

Working Code in Spring Boot 2.x:

protected Map<String, Object> returnSavedObject(String packageName, String className, Map<String, Object> requestDto, String moduleName, Long pkId) {
    try {
        // Dynamically creates module object
        Class moduleClass = Class.forName(packageName + className + "Dto");
        Object object = returnObjectFromRequest(requestDto, moduleClass, moduleName, pkId);

        // Loads bean for the given Module serviceImpl class
        Object serviceObject = applicationContext.getBean(className.substring(0, 1).toLowerCase() + className.substring(1) + "ServiceImpl");

        // Invokes the save method on serviceImpl class
        Object savedObject = serviceObject.getClass().getMethod("save", moduleClass).invoke(serviceObject, object);
        log.info("savedObject {}", savedObject);
        return fieldHelper.objectToMapConverter(savedObject);
    } catch (Exception e) {
        throw new NotFoundException(MessageCode.error(ApplicationErrorCode.MODULE_CLASS_NOT_FOUND.getKey(), ApplicationErrorCode.MODULE_CLASS_NOT_FOUND.getValue()));
    }
}

Issue After Migrating to Spring Boot 3.2.1:

The code encountered a 'java.lang.NoSuchMethodException' exception, and during debugging, we observed 'java.lang.NoSuchMethodException:jdk.proxy2.$Proxy292.save(dto)'. It appears that the getBean method in the application context is returning a proxy bean instead of the actual bean.

To Address the above Issue, We added below code to get actual bean object.

    Class<?> targetClass = AopProxyUtils.ultimateTargetClass(serviceObject);
protected Map<String, Object> returnSavedObject(String packageName, String className, Map<String, Object> requestDto, String moduleName, Long pkId) {
    try {
        // Dynamically creates module object
        Class moduleClass = Class.forName(packageName + className + "Dto");
        Object object = returnObjectFromRequest(requestDto, moduleClass, moduleName, pkId);

        // Resolves the actual target class from the proxy
        String beanClassName = className.substring(0, 1).toLowerCase() + className.substring(1) + "ServiceImpl";
        Object serviceObject = applicationContext.getBean(beanClassName);
        Class<?> targetClass = AopProxyUtils.ultimateTargetClass(serviceObject);

        // Invokes the save method on the target class
        Object savedObject = targetClass.getMethod("save", moduleClass).invoke(targetClass, object);
        log.info("savedObject {}", savedObject);
        return fieldHelper.objectToMapConverter(savedObject);
    } catch (Exception e) {
        throw new NotFoundException(MessageCode.error(ApplicationErrorCode.MODULE_CLASS_NOT_FOUND.getKey(), ApplicationErrorCode.MODULE_CLASS_NOT_FOUND.getValue()));
    }
}

New Issue Encountered:

The updated code now throws a 'java.lang.IllegalArgumentException': "Object is not an instance of declaring class."

Could you help me resolve this error ?

2

There are 2 answers

1
Gopikrishna On BEST ANSWER

Below code helped me to solve the above problem

Object serviceBean = applicationContext.getBean(beanClassName);
if (AopUtils.isAopProxy(serviceBean) && serviceBean instanceof Advised) {
    serviceBean = ((Advised) serviceBean).getTargetSource().getTarget();
    }
Object savedObject = serviceBean.getClass().getMethod("save", moduleClass).invoke(serviceBean, object);
0
M. Deinum On

Your invoke should still call the serviceObject as that is what you want to invoke it on. Instead of this contraption I would suggest using the ReflectionUtils from Spring which handles all this for you.

// Dynamically creates module object
var moduleClass = Class.forName(packageName + className + "Dto");
var object = returnObjectFromRequest(requestDto, moduleClass, moduleName, pkId);

// Loads bean for the given Module serviceImpl class
var serviceObject = applicationContext.getBean(className.substring(0, 1).toLowerCase() + className.substring(1) + "ServiceImpl");

// Invokes the save method on serviceImpl class
var saveMethod = ReflectionUtils.findMethod(serviceObject.getClass(), "save", moduleClass);
var savedObject = ReflectionUtils.invokeMethod(saveMethod, serviceObject, object);
log.info("savedObject {}", savedObject);
return fieldHelper.objectToMapConverter(savedObject);

Took the liberty to make it more Java 21 as well. The ReflectionUtils will identify the proxy when finding the method. The created proxy should at least be an implementation of the interface of the class or extend the service impl.