[Java] uses reflection to execute the methods specified by Spring container Bean, and supports automatic calling of multiple parameters

Keywords: Java Spring JDK Junit

catalog

Usage scenarios

  1. Input the scheduled task into the database (the advantage of this is the visualization of the scheduled task, and the execution time of each task can be modified dynamically), and execute the corresponding method through reflection;
  2. Implement a simple HTTP request processing framework with Netty
  3. Other businesses that need to use reflection to execute Spring methods can also

objective

Reflection is mentioned in many articles, but for the processing of method parameters, the type is clear, and dynamic conversion according to actual parameters is not supported. This article provides a way to realize dynamic call of method parameters.
You can also write more reusable and extensible code by using the method in this paper and combining your own business scenarios. You are welcome to point out the mistakes in the article. If you have better ideas, you can comment on them below. Let's discuss them together.
Welcome to forward, please indicate the source.

Implementation mode

Premise:

Be clear about the classes and methods that need to be implemented.

thinking

  1. Obtain the classes to be executed through the spring container. Note: the classes obtained from the spring container may be proxied by JDK or CGLIB (depending on your environment configuration);
  2. Get the executed method object;
  3. The actual parameter List of encapsulation method only supports automatic conversion of basic type wrapper class, String, object, Map and other parameter types
  4. Executing the invoke method of method

Core class

@Service
public class ReflectionService {

    @Resource
    private ApplicationContext applicationContext;

    private static final List<Class> WRAP_CLASS = Arrays.asList(Integer.class, Boolean.class, Double.class,Byte.class,Short.class, Long.class, Float.class, Double.class, BigDecimal.class, String.class);


    /**
     * Reflecting the entry to call spring bean methods
     * @param classz Class name
     * @param methodName Method name
     * @param paramMap Actual parameters
     * @throws Exception
     */
    public void invokeService(String classz, String methodName, Map<String,Object> paramMap) throws Exception {
        if(!applicationContext.containsBean(classz)) {
            throw new RuntimeException("Spring No corresponding Bean");
        }

        // Get proxy object from Spring (may be proxied by JDK or CGLIB)
        Object proxyObject = applicationContext.getBean(classz);

        // Get the method of proxy object execution
        Method method = getMethod(proxyObject.getClass(), methodName);

        // Get target object in proxy object
        Class target = AopUtils.getTargetClass(proxyObject);

        // Get the method of the target object, why get the method of the target object: only the target object can get the method name of the parameter through DefaultParameterNameDiscoverer, and the proxy object can't get the parameter name due to the possibility of being proxied by JDK or CGLIB
        Method targetMethod = getMethod(target, methodName);

        if(method == null) {
            throw new RuntimeException(String.format("Can't find%s method", methodName));
        }

        // Get parameters of method execution
        List<Object> objects = getMethodParamList(targetMethod, paramMap);

        // Execution method
        method.invoke(proxyObject, objects.toArray());
    }

    /**
     * Get method actual parameter, basic type not supported
     * @param method
     * @param paramMap
     * @return
     */
    private List<Object> getMethodParamList(Method method, Map<String, Object> paramMap) throws Exception {
        List<Object> objectList = new ArrayList<>();

        // Using the class provided by Spring to get the method parameter name
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] param =  nameDiscoverer.getParameterNames(method);

        for (int i = 0; i < method.getParameterTypes().length; i++) {
            Class<?> parameterType = method.getParameterTypes()[i];

            Object object = null;
            // The basic type is not supported. The wrapper class is supported
            if(WRAP_CLASS.contains(parameterType)) {
                if(param != null && paramMap.containsKey(param[i])){
                    object = paramMap.get(param[i]);

                    object = ConvertUtils.convert(object, parameterType);
                }

            }else if (!parameterType.isPrimitive() ) {
                object = getInstance(parameterType);

                // assignment
                BeanUtils.populate(object, paramMap);
            }

            objectList.add(object);
        }

        return objectList;
    }

    /**
     * Get type instance
     * @param parameterType
     * @return
     * @throws Exception
     */
    private Object getInstance(Class<?> parameterType) throws Exception {
        if(parameterType.isAssignableFrom(List.class)) {
            return  new ArrayList();

        }else if(parameterType.isAssignableFrom(Map.class)) {
            return new HashMap();
        }else if(parameterType.isAssignableFrom(Set.class)) {
            return  new HashSet();
        }
        return parameterType.newInstance();
    }

    /**
     * Get target method
     * @param proxyObject
     * @param methodStr
     * @return
     */
    private Method getMethod(Class proxyObject, String methodStr) {
        Method[] methods = proxyObject.getMethods();

        for(Method method : methods) {
            if(method.getName().equalsIgnoreCase(methodStr)) {
                return method;
            }
        }

        return null;
    }
}

test method

package com.ywqonly.springtest.reflection;

import com.ywqonly.springtest.reflection.service.impl.ReflectionService;
import com.ywqonly.springtest.reflection.vo.CarVO;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringReflectionTest {

    @Resource
    private ReflectionService reflectionService;

    @Test
    public void paramTest() throws Exception {
        Map<String, Object>  paramMap = new HashMap<>();

        paramMap.put("carName", "bmw");
        paramMap.put("speed", "1");
        reflectionService.invokeService("carServiceImpl", "start", paramMap);
    }

    @Test
    public void objectTest() throws Exception {
        Map<String, Object>  paramMap = new HashMap<>();

        paramMap.put("carName", "bmw");
        paramMap.put("speed", "2");
        reflectionService.invokeService("carServiceImpl", "startByVO", paramMap);
    }

    @Test
    public void mapTest() throws Exception {
        Map<String, Object>  paramMap = new HashMap<>();

        paramMap.put("carName", "bmw");
        paramMap.put("speed", "3");
        reflectionService.invokeService("carServiceImpl", "startByMap", paramMap);
    }

}

Source sharing

GITHUB source address

Posted by Chris Val Kef on Sat, 13 Jun 2020 03:06:01 -0700