Created
May 21, 2018 13:07
-
-
Save kk17/c51ceec11169d52def287510db0d68b2 to your computer and use it in GitHub Desktop.
在命令行参数中指定spring BeanName,方法和方法参数即可运行对应的方法
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.google.common.collect.Lists; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import org.springframework.beans.BeansException; | |
import org.springframework.boot.ApplicationArguments; | |
import org.springframework.boot.ApplicationRunner; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.context.ApplicationContextAware; | |
import org.springframework.context.annotation.Profile; | |
import org.springframework.stereotype.Component; | |
import java.io.IOException; | |
import java.lang.annotation.Annotation; | |
import java.lang.annotation.Documented; | |
import java.lang.annotation.ElementType; | |
import java.lang.annotation.Retention; | |
import java.lang.annotation.RetentionPolicy; | |
import java.lang.annotation.Target; | |
import java.lang.reflect.Method; | |
import java.text.MessageFormat; | |
import java.util.List; | |
/** | |
* 在命令行参数中指定spring BeanName,方法和方法参数即可运行对应的方法 | |
* @author kk(zk.chan007@gmail.com) | |
*/ | |
@Component | |
@Profile("methodRunner") | |
public class SpringBeanMethodCommandLineRunner implements ApplicationRunner, ApplicationContextAware{ | |
private ApplicationContext applicationContext; | |
private static final Logger log = LoggerFactory.getLogger(SpringBeanMethodCommandLineRunner.class); | |
public SpringBeanMethodCommandLineRunner() { | |
log.info("Enabled Runner"); | |
} | |
@SuppressWarnings("AlibabaUndefineMagicConstant") | |
@Override | |
public void run(ApplicationArguments args) throws Exception { | |
final List<String> nonOptionArgs = args.getNonOptionArgs(); | |
if (nonOptionArgs.isEmpty()) { | |
log.warn("Could not find non-option args; disable runner"); | |
return; | |
} | |
// Could not find matching type descriptor for requested Java class [java.util.Set]; using fallback | |
if (nonOptionArgs.size() < 2) { | |
log.warn("Non-option args '{}' is insufficient; disable runner", nonOptionArgs); | |
return; | |
} | |
final String beanName = nonOptionArgs.get(0); | |
final String methodName = nonOptionArgs.get(1); | |
final List<String> methodTextArgs = nonOptionArgs.subList(2, nonOptionArgs.size()); | |
final Object bean = this.applicationContext.getBean(beanName); | |
log.info("Found bean with name '{}', bean class '{}'", beanName, bean.getClass()); | |
final Class<?> beanClass = bean.getClass(); | |
// get all public methods | |
final Method[] methods = beanClass.getMethods(); | |
List<Method> candidates = Lists.newLinkedList(); | |
for (Method method : methods) { | |
if (method.getName().equalsIgnoreCase(methodName)) { | |
candidates.add(method); | |
} | |
} | |
if (candidates.isEmpty()) { | |
throw new IllegalStateException(String.format("Could not find matching method for requested method name [%s]; disable runner", methodName)); | |
} | |
if (candidates.size() > 1) { | |
throw new IllegalStateException(("More than one method found; disable runner")); | |
} | |
Method method = candidates.get(0); | |
log.info("Found bean method with name '{}', method '{}'", method, method); | |
Object[] methodArgs = buildArgs(method, methodTextArgs); | |
final Class<?> returnType = method.getReturnType(); | |
log.info("Invoking bean method"); | |
final Object result = method.invoke(bean, methodArgs); | |
if (returnType != void.class) { | |
logResult(result); | |
} | |
log.info("Invoked bean method"); | |
} | |
private void logResult(Object result) { | |
log.info("Method return with result:\n-----------\n{}\n----------\n", result); | |
} | |
private Object[] buildArgs(Method method, List<String> methodTextArgs) throws IllegalAccessException, InstantiationException { | |
List<Object> args = Lists.newLinkedList(); | |
final Class<?>[] types = method.getParameterTypes(); | |
for (int i = 0; i < types.length; i++) { | |
Class<?> type = types[i]; | |
final CommandLineArg annotation = findParameterAnnotation(method, i, CommandLineArg.class); | |
String textValue; | |
if (methodTextArgs.size() >= i + 1) { | |
textValue = methodTextArgs.get(i); | |
} else { | |
if (annotation == null) { | |
// Parameter 0 of constructor in com.netease.one.item.controller.FtagController required a bean of type 'com.netease.one.item.service.FtagService' that could not be found. | |
String message = MessageFormat.format("Parameter index:[{0}] type:[{1}] is not no provided from command line and has no default value.", i, type); | |
throw new IllegalArgumentException(message); | |
} | |
textValue = annotation.defaultValue(); | |
} | |
Converter converter; | |
Class<?> converterClazz = JsonConverter.class; | |
try { | |
if (annotation != null) { | |
converterClazz = annotation.converter(); | |
} | |
converter = (Converter) converterClazz.newInstance(); | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
Object arg = converter.convert(textValue, type); | |
args.add(arg); | |
} | |
return args.toArray(); | |
} | |
@SuppressWarnings("unchecked") | |
private <T extends Annotation> T findParameterAnnotation(Method method, int paramterIndex, Class<T> annotaionClass){ | |
final Annotation[][] annotations = method.getParameterAnnotations(); | |
Annotation[] typeAnnotations = annotations[paramterIndex]; | |
for (Annotation typeAnnotation : typeAnnotations) { | |
if (typeAnnotation.annotationType() == annotaionClass) { | |
return (T) typeAnnotation; | |
} | |
} | |
return null; | |
} | |
@Override | |
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { | |
this.applicationContext = applicationContext; | |
} | |
/** | |
* 命令行参数转换接口,将String类型参数转换成方法需要的参数类型 | |
*/ | |
public interface Converter{ | |
/** | |
* 命令行参数转换成目标参数类型 | |
* @param textArg 命令行传入的字符串格式参数 | |
* @param clazz 目标参数类型类定义 | |
* @param <T> 目标参数类型 | |
* @return 目标参数类型实例 | |
*/ | |
<T> T convert(String textArg, Class<T> clazz); | |
} | |
public static class JsonConverter implements Converter{ | |
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); | |
@SuppressWarnings("unchecked") | |
@Override | |
public <T> T convert(String textArg, Class<T> clazz) { | |
try { | |
// 如果参数需要字符串而传入的参数没有使用引号包含(除去字符串值本身是null的情况)JSON反序列化会出错,直接返回字符串就好 | |
//noinspection AlibabaUndefineMagicConstant | |
if (CharSequence.class.isAssignableFrom(clazz) && !textArg.startsWith("\"") && !"null".equals(textArg)) { | |
return (T) textArg; | |
} | |
return OBJECT_MAPPER.readValue(textArg, clazz); | |
} catch (IOException e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
/** | |
* 注解用于标明如果方法从命令行被调用时方法参数的一些值类型的转换策略 | |
* @author kk(zk.chan007@gmail.com) | |
*/ | |
@Target(ElementType.PARAMETER) | |
@Retention(RetentionPolicy.RUNTIME) | |
@Documented | |
public @interface CommandLineArg { | |
/** | |
* 指定参数如果没有在命令行调用提供时将使用的默认值 | |
*/ | |
String defaultValue() default ""; | |
/** | |
* 指定从命令行String类型参数转换到Java参数类型的转换类,如果没有指定,默认使用Json进行转换 | |
*/ | |
Class<? extends Converter> converter() default JsonConverter.class; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment