JDK 7 java-shell的使用, 自编写框架 - Go语言中文社区

JDK 7 java-shell的使用, 自编写框架


首先 , 传送门 spring-shell-java

     由于某一些原因, 需要自己编写一个运行程序, 实现在命令行中使用命令行的方式作出数据管理, 由于JDK版本还有其他一些因素, 自己开发了一个运行框架, 并且公布源码 , 希望和大家一个讨论, 改进.

目录

 

1、概述

1、1、使用的技术

 1、1、1:SpringBoot 

1、1、2: common-cli

1、1、3: maven 

2、使用

2、1:类上面

2、2:方法上面

3、注意事项

3、1: 命令行重复

3、2: 命令行异常处理

3、3 : 日记管理

3、源码讲解

3、1: 启动

3、2: 命令行的生成

3、3: 命令的执行

3、4: 输出


1、概述

1、1、使用的技术

 1、1、1:SpringBoot 

本人使用的是做为真个的框架的基础

@SpringBootApplication
public class SpringShellJavaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringShellJavaApplication.class, args);
    }
}

从这儿就可以看出, 因为使用到了IOC , AOP倒是没有使用到, 如果可以, 你可以自己添加, 所以, 你懂的.

1、1、2: common-cli

不要惊慌, 这个只是是对我们数据的命令还有参数的解析, 所以这儿拿出来只是为了引起注意

1、1、3: maven 

    这个就节约了, maven 是做到的我们整个项目的构建, 包的管理, 可以很方便的做到这个, 其实, 还有ant, gradle , sbt, 这些都需要自己去折腾的,对maven 比较了解, 深一些

 

2、使用

源码下载下来之后, 使用的很简单.

2、1:类上面

和 我们的SpringMVC 一样的, 使用注解就可以, 在需要的类上面添加 注解 

@ShellComponent(name = "test")

name 是这个类的一个别名, 如果不取, 则会按照当前的类的名字生成默认的命令

2、2:方法上面

在方法上的使用, 和 @PostMapping 这样的注解一样的哦

@ShellMethod(name = "method", detail = "")
    public void method(
            @ShellOptions(detail = "Ip地址") String address,
            @ShellOptions(detail = "端口号") String id,
            @ShellOptions(detail = "使用的年限") String year) throws FileNotFoundException {
        throw new FileNotFoundException(address + " " + id + " " + year);
    }

好啦, 这样就完成了, 是不是很简单

最后的效果是这样的

在Input的后面输入命令就好了, 上面的信息是内置的几个命令, 可以查看全部的命令, 也可以查看某一个命令的详细信息.

如果使用 window 的 cmd 或者是linux的命令行, 这样应该会熟悉.

3、注意事项

3、1: 命令行重复

 1、如果在一个@ShellComponent 里面有相同的@ShellMethod,在启动的时候失败.

2、如果在整个项目中, @ShellComponent + @ShellMethod 有两个相同的, 也会启动失败.

3、2: 命令行异常处理

1、输入异常处理:

    当前只是添加了基本的管理, 对于参数的验证没有任何的处理, 都当作数据处理, 只是输入简单的字符, 后期后扩展, 输入json, 或者是xml的支持.

输入的处理, 需要调用者自己做处理, 也可以fork 代码, 自己扩展.

2、输出/运行时处理:

 对于的异常的处理, 记录在日志中, 提供给后面做排查查阅.还有就是会直接显示在命令上.

第一, 输入异常

    命令行输入错误或者是参数输入错误

 

第二,运行时的异常.

并且查看日志会有如下的记录

3、3 : 日记管理

日志会每天都换一个文件. 并且会对异常分开存储

所有的日志的配置都在这儿, 有详细的说明

3、源码讲解

3、1: 启动

启动很简单, 就是springboot的启动, 可以使用命令启动, 也可以使用 集成工具工具, 还可以使用jar启动, 都可以的

@SpringBootApplication
public class SpringShellJavaApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringShellJavaApplication.class, args);
    }
}

3、2: 命令行的生成

在启动的时候, spring 运用 ioc, 会给我们初始化需要使用的bean, 单列的哦, 在使用的时候就直接@注解就可以使用了, 但是这儿我门需要去管理一下

在CommandShellConfig 生成了一个@Bean  ShellMethodTargetRegistrar 直接使用的new 生成的一个bean哦, 这也是, 一种生成bean的方法, spring 里面也是这样生成的.

然后查看 ShellMethodTargetRegistrar 里面有一个

    /**
     * 注册方法
     */
    @PostConstruct
    public void register() {}

看到了吧, 在调用 new ShellMethodTargetRegistrar 的时候, 就会自动执行这个方法, 然后就是扫描整个bean 容器, 找到有 @ShellCompent这个注解, 在找到有 @ShellMethod的方法, 生成命令 还有参数

@PostConstruct
    public void register() {
        //获取全部的类的信息
        Map<String, Object> commandBeans = applicationContext.getBeansWithAnnotation(ShellComponent.class);
        //获取方法参数名
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        for (String beanName : commandBeans.keySet()) {
            String bName = beanName;
            Object bean = commandBeans.get(beanName);
            Class<?> clazz = bean.getClass();
            ShellComponent shellOptions = clazz.getAnnotation(ShellComponent.class);
            if (shellOptions != null && StringUtils.isNoneBlank(shellOptions.name())) {
                bName = shellOptions.name();
            }
            Map<String, Method> methods = Maps.emptys();
            List<ShellMethodTarget> shellMethodTargets = Lists.empty();
            ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
                @Override
                public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                    ShellMethod shellMapping = method.getAnnotation(ShellMethod.class);
                    String name = shellMapping.name();
                    if (StringUtils.isEmpty(name)) {
                        name = method.getName();
                    }
                    if (methods.get(name) != null) {
                        throw new IllegalArgumentException(
                                String.format("Illegal registration for command '%s': Attempt to register both '%s' and '%s'", name, methods.get(name), method));
                    }
                    methods.put(name, method);
                    String detail = shellMapping.detail();
                    List<ShellMethodParameter> methodParameterMap = Lists.empty();
                    String[] param = discoverer.getParameterNames(method);
                    this.validateShortName(beanName, method, param);
                    Annotation[][] annotateds = method.getParameterAnnotations();
                    Annotation[] annotations;
                    Class[] paramClazzs = method.getParameterTypes();
                    if (param.length != paramClazzs.length && annotateds.length != paramClazzs.length) {
                        try {
                            throw new ShellParameterException(
                                    String.format("Illegal registration for command '%s': All Paramter Must Has Option:@ShellOptions", name)
                            );
                        } catch (ShellParameterException e) {
                            LoggerUtils.error(e.getMessage());
                            e.printStackTrace();
                        }
                    } else {
                        for (int i = 0; i < param.length; i++) {
                            annotations = annotateds[i];
                            methodParameterMap.add(new ShellMethodParameter(param[i], paramClazzs[i], join("", annotations)));
                        }
                        ShellMethodTarget target = new ShellMethodTarget(method, bean, name, detail, methodParameterMap);
                        shellMethodTargets.add(target);
                    }
                }

                /**
                 * 自定义方法
                 * @param param
                 */
                private void validateShortName(String name, Method method, String[] param) {
                    Map<String, String> mSets = Maps.emptys();
                    String shortName;
                    if (null != param && param.length > 0) {
                        for (String s : param) {
                            shortName = StringUtils.shortName(s);
                            if (mSets.keySet().contains(shortName)) {
                                throw new IllegalArgumentException(
                                        String.format("Illegal registration for class  '%s' ," +
                                                " method '%s': Attempt to register both parameter option '-%s'(%s) and '-%s'(%s)", name, method, shortName, mSets.get(shortName), shortName, s));
                            }
                            mSets.put(shortName, s);
                        }
                    }
                }
            }, new ReflectionUtils.MethodFilter() {
                @Override
                public boolean matches(Method method) {
                    return method.getAnnotation(ShellMethod.class) != null;
                }
            });
            commands.put(bName, shellMethodTargets);
        }

 

3、3: 命令的执行

在获取到命令行之后, 解析出参数, 就会去执行 执行在这个类里面:CommandShellRunner.直接就是去调用对应的方法

@Override
    public void run(ApplicationArguments applicationArguments) {
        PrintWriter pw = new PrintWriter(System.out);
        ShellPrint.printFirstMessage();
        while (true) {
            pw.print("nInput>:");
            pw.flush();
            String commandLine = sc.nextLine();  //读取字符串型输入
            if (StringUtils.isNotBlank(commandLine)) {
                if (ActionEnums.EXIT.getAction().equalsIgnoreCase(commandLine)) {
                    System.exit(0);
                }
                try {
                    if(this.validate(commandLine)){
                        ShellCommandParse parse = new ShellCommandParse(commands.get(getGroupName(commandLine)));
                        Object[] args = parse.getParameterValue(commandLine);
                        if (parse.getCurrentMethod().getBean().getClass().getName().equals("org.poem.core.handler.HelpHandler")) {
                            args = new Object[1];
                            String command = getCommand(commandLine);
                            if (StringUtils.isNotBlank(command)) {
                                args[0] = command;
                            }
                        }
                        executor(parse.getCurrentMethod(), args);
                    }
                } catch (ParseException e) {
                    //参数转换异常
                    LoggerUtils.error(e.getMessage(),e);
                    ShellPrint.printMsg(e.getMessage());
                } catch (ShellCommandException e) {
                    //输入的命令异常
                    LoggerUtils.error(e.getMessage(),e);
                    ShellPrint.printMsg(e.getMessage());
                }catch (Exception e){
                    //调用的方法中出现异常
                    if(e instanceof UndeclaredThrowableException){
                        LoggerUtils.error(e);
                        ShellPrint.printMsg("错误信息:"+ ((UndeclaredThrowableException) e).getUndeclaredThrowable().getLocalizedMessage());
                    }
                    else{
                        LoggerUtils.error(e);
                        ShellPrint.printMsg(e.getMessage());
                    }
                }
            } else {
                System.err.println("n");
            }
        }
    }

这儿有一个while(true) 一直等着命令的输入, 然后去解析, 在执行方法, 这儿使用了反射去获取调用的类, 调用的方法

/**
     * 执行器
     *
     * @param shellMethodTarget
     * @param parameters
     */
    private void executor(ShellMethodTarget shellMethodTarget, Object[] parameters) {
        try {
            Object clsObj = shellMethodTarget.getBean();
            Method method = shellMethodTarget.getMethod();
            String name = method.getReturnType().getSimpleName();
            if (!"void".equals(name)) {
                Object result = ReflectionUtils.invokeMethod(method, clsObj, parameters);
                ShellPrint.printResult(result);
            } else {
                    ReflectionUtils.invokeMethod(method, clsObj, parameters);
            }
        } catch (IllegalArgumentException e) {
            LoggerUtils.error(e);
            ShellPrint.printMsg(e.getMessage());
        } catch (Exception e) {
            LoggerUtils.error(e);
            if (e instanceof UndeclaredThrowableException) {
                ShellPrint.printMsg("错误信息:" + ((UndeclaredThrowableException) e).getUndeclaredThrowable().getLocalizedMessage());
            } else {
                ShellPrint.printMsg(e.getMessage());
            }
        } finally {
            //打印数据
            ShellPrint.printMsg("n");
        }
    }

3、4: 输出

  1、正常的输出:

正常输出, 是用户自己的输出, 或者是集成SObject, 直接输出, 可以覆盖掉SObject, 自定义输出

/**
 * super class
 */
public class SObject implements Serializable {

    /**
     * default toString
     * @return
     */
    public String sToString(){
        return  super.toString();
    }
}

2、异常输出:

异常输出, 是代码运行错误的输出, 会打印出对于的错误, 并且, 会记录在日志里.

    catch (Exception e) {
            LoggerUtils.error(e);
            if (e instanceof UndeclaredThrowableException) {
                ShellPrint.printMsg("错误信息:" + ((UndeclaredThrowableException)         e).getUndeclaredThrowable().getLocalizedMessage());
            } else {
                ShellPrint.printMsg(e.getMessage());
            }

完成.

后续会增加json, 生成文档的功能, 请期待, 如果有问题, 可以在github上或者在文章下写.

多交流

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/poem_2010/article/details/86030842
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-01 21:15:38
  • 阅读 ( 1107 )
  • 分类:Go Web框架

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢