Java反射借助MethodInvokingFactoryBean实现加载Spring自动注入,

反射基础学习可以参考我之前写的一篇文章:

https://blog.csdn.net/SELECT_BIN/article/details/80620739

先说下业务背景:

要执行方法的类名/路径和方法名都以动态的形式传
        
        这个时候第一个想到的是Java的反射,于是写了一下:
        
        这个时候突然发现用jdk反射获取对象的形式和new的对象性质一样的,
        
        也就是说Spring的Autowired不生效了,
    
        这时候想到Spring的ApplicationContextAware,现在Spring boot

加载maven配置:

        
            com.csc.permission
            permission-sec-cert-client
            1.0.7-SNAPSHOT
        

实例:

普通反射:

package com.unicom.kc.task.job.service.impl;

import com.unicom.kc.task.job.service.TaskJobCompensateService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;

/**
 * @version 1.0
 * @ClassName TaskJobCompensateServiceImlp
 * @Description ***************
 * @Author 74981
 * @Date 2019/4/15 11:01
 */
@Service
public class TaskJobCompensateServiceImlp implements TaskJobCompensateService {

    private static final Log logger = LogFactory.getLog(TaskJobCompensateServiceImlp.class);

    @Override
    public void reflectCompensate(String className, String methodName){
        Class demo = null;
        logger.info("入参:className=" + className + ";methodName=" + methodName);
        try {
            demo = Class.forName(className);
        } catch (Exception e) {
            logger.error("获取执行Class失败!");
            e.printStackTrace();
        }
        if (null == demo){
            logger.error("获取执行Class失败!");
            return;
        }

        try{
            Method method = demo.getMethod(methodName);
            method.invoke(demo.newInstance());
        }catch (Exception e){
            logger.error("获取执行Method失败!");
            e.printStackTrace();
        }
    }
}

需要加载Spring特性时:

先说一种静态加载的,这个时候要加载的类名是确定的,实现起来就比较容易:

通过MethodInvokingFactoryBean工厂Bean,可以将指定方法返回值注入成为目标Bean的属性值,MethodInvokingFactoryBean用来获得指定方法的返回值,该方法可以是静态方法 
也可以是实例方法。 
获得的方法返回值既可以被注入到指定Bean实例的指定属性,也可以直接定义成Bean实例。 

上代码:

package com.unicom.kc.task.job.service.impl;

import com.unicom.kc.task.job.service.TaskJobCompensateService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

import java.lang.reflect.Method;

/**
 * @version 1.0
 * @ClassName TaskJobCompensateServiceImlp
 * @Description *****************
 * @Author 74981
 * @Date 2019/4/15 11:01
 */
@Service
public class TaskJobCompensateServiceImlp implements TaskJobCompensateService {

    private static final Log logger = LogFactory.getLog(TaskJobCompensateServiceImlp.class);

    @Autowired
    ApplicationContext applicationContext;

    @Override
    public void reflectCompensate(String className, String methodName){
        Class demo = null;
        logger.info("****入参:className=" + className + ";methodName=" + methodName);
        try {
            demo = Class.forName(className);
        } catch (Exception e) {
            logger.error("****获取执行Class失败!");
            e.printStackTrace();
        }
        if (null == demo){
            logger.error("****获取执行Class失败!");
            return;
        }
        try {
            Object beanObj = applicationContext.getBean(demo);
			//当知道实例名称时,强转为需要的实例
            LoadDataServieImpl instance = (LoadDataServieImpl) beanObj;
            //调用实例中的方法--如果有参数的话,在上面传参的时候要考虑,这里是简单Demo,不完善
            instance.dealSchRelea();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

那么关键问题来了,这个时候如果要加载的实例是可变的呢?

这个时候要动态加载的类名和调用的方法名都是不确定,怎么实现呢:

可以借助Spring的反射工具类 ReflectionUtils手动实现下Spring的AOP:

解释下实现的思路:

ReflectionUtils里面有个findMethod(Class clazz, String name, Class... paramTypes)的实例

源码:

    /**
	 * Attempt to find a {@link Method} on the supplied class with the supplied name
	 * and parameter types. Searches all superclasses up to {@code Object}.
	 * 

Returns {@code null} if no {@link Method} can be found. * @param clazz the class to introspect * @param name the name of the method * @param paramTypes the parameter types of the method * (may be {@code null} to indicate any signature) * @return the Method object, or {@code null} if none found */ public static Method findMethod(Class clazz, String name, Class... paramTypes) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(name, "Method name must not be null"); Class searchType = clazz; while (searchType != null) { Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); for (Method method : methods) { if (name.equals(method.getName()) && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { return method; } } searchType = searchType.getSuperclass(); } return null; } /** * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache * in order to avoid the JVM's SecurityManager check and defensive array copying. * In addition, it also includes Java 8 default methods from locally implemented * interfaces, since those are effectively to be treated just like declared methods. * @param clazz the class to introspect * @return the cached array of methods * @see Class#getDeclaredMethods() */ private static Method[] getDeclaredMethods(Class clazz) { Assert.notNull(clazz, "Class must not be null"); Method[] result = declaredMethodsCache.get(clazz); if (result == null) { Method[] declaredMethods = clazz.getDeclaredMethods(); List defaultMethods = findConcreteMethodsOnInterfaces(clazz); if (defaultMethods != null) { result = new Method[declaredMethods.length + defaultMethods.size()]; System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); int index = declaredMethods.length; for (Method defaultMethod : defaultMethods) { result[index] = defaultMethod; index++; } } else { result = declaredMethods; } declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result)); } return result; }

源码实现并不难看懂,这里不再详细解释,

关键代码:

            //这里三个参数,demoClass是获取要反射类的class,methodName是要调用的demoClass里的方法,
            // new Class[]{String.class}里面的参数类型一定要和被调用方法中的参数类型保持一致
            Method method = ReflectionUtils.findMethod(demoClass, methodName, new Class[]{String.class});

取出待调用方法之后,使用Object invokeMethod(Method method, Object target, Object... args) 实例化并invoke方法:

源码贴一下:

    /**
	 * Invoke the specified {@link Method} against the supplied target object with the
	 * supplied arguments. The target object can be {@code null} when invoking a
	 * static {@link Method}.
	 * 

Thrown exceptions are handled via a call to {@link #handleReflectionException}. * @param method the method to invoke * @param target the target object to invoke the method on * @param args the invocation arguments (may be {@code null}) * @return the invocation result, if any */ public static Object invokeMethod(Method method, Object target, Object... args) { try { return method.invoke(target, args); } catch (Exception ex) { handleReflectionException(ex); } throw new IllegalStateException("Should never get here"); }

实例:

            //取出待调用方法的实例之后对方法invoke;
            Map result = (Map) ReflectionUtils.invokeMethod(method, applicationContext.getBean(demoClass), new Object[]{param});

 这边写了一个简单实现小栗子:

比如我要调用“com.unicom.kc.task.job.service.impl.WorkPatchJobServiceImpl”类下面的“public void noticeSend(String record)”方法,入参类型为String:

实现类:

package com.unicom.kc.task.job.service.impl;

import com.unicom.kc.task.job.service.TaskJobCompensateService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

/**
 * @version 1.0
 * @ClassName TaskJobCompensateServiceImlp
 * @Author 74981
 * @Date 2019/4/15 11:01
 */
@Service
public class TaskJobCompensateServiceImlp implements TaskJobCompensateService {

    private static final Log logger = LogFactory.getLog(TaskJobCompensateServiceImlp.class);

    @Autowired
    ApplicationContext applicationContext;


    @Override
    public void reflectCompensateRef(String className, String methodName, String param) throws Exception {

        Class demoClass = null;
        logger.info("****入参:className=" + className + ";methodName=" + methodName);
        try {
            demoClass = Class.forName(className);
        } catch (Exception e) {
            logger.error("****获取执行Class失败!");
            e.printStackTrace();
        }
        if (null == demoClass){
            logger.error("****获取执行Class失败!");
            return;
        }
        try {
            if (StringUtil.isEmpty(param)){
                //区分有参和无参构造器
                Method method = ReflectionUtils.findMethod(demoClass, methodName);
                ReflectionUtils.invokeMethod(method, applicationContext.getBean(demoClass));
            } else {
                //这里三个参数,demoClass是获取要反射类的class,methodName是要调用的demoClass里的方法,
                // new Class[]{String.class}里面的参数类型一定要和被调用方法中的参数类型保持一致
                Method method = ReflectionUtils.findMethod(demoClass, methodName, new Class[]{String.class});
                //取出待调用方法的实例之后对方法invoke;
                ReflectionUtils.invokeMethod(method, applicationContext.getBean(demoClass), new Object[]{param});
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里有个中间归结的方法,算是为了反射而反射,主要为了测试Spring的自动注入是否生效,实际业务上可灵活运用:

    public void noticeSendListener(String record) {
        logger.info("--xxxxxx start--;param:"+record);
        noticeSendService.noticeSend(record);
        logger.info("--xxxxxxxx end--" );
    }

实际业务这里可以随意写,没有限制:

    public void noticeSend(String record) {
        logger.info("============= I'm in this noticeSend; =begin=; paramater: " + record);
    }

 

单元测试:

    @Test
    public void reflectCompensate() throws Exception {
        taskJobCompensateService.reflectCompensateRef("com.unicom.kc.task.job.service.impl.WorkPatchJobServiceImpl",
                "noticeSendListener","");
    }

这里看到我们已经可以实现加载动态类中的动态方法,日志打印:

 完结;

最后,反射用起来还是很方便的,但是不是万不得已,不建议在系统中使用,从运维的角度来看,反射对封装性的破坏和性能的影响很多时候并不值得。

你可能感兴趣的:(Spring)