反射基础学习可以参考我之前写的一篇文章:
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","");
}
这里看到我们已经可以实现加载动态类中的动态方法,日志打印:
完结;
最后,反射用起来还是很方便的,但是不是万不得已,不建议在系统中使用,从运维的角度来看,反射对封装性的破坏和性能的影响很多时候并不值得。