JavaAnnotation运用到实际项目中

来源:互联网 发布:数据中心大二层网络 编辑:程序博客网 时间:2024/05/02 15:59

我们讨论的前提是用JAVA实现一个简单的工作流,工作流的节点中包含分支,但是不讨论会签的情况。
曾几何时,对于IT职场沉浮十年以上的人来说,这个功能应该是简单的有点无语的功能,对于经验丰富的人来说,实现的方案真是数不胜数,比如把流程节点存到数据库中以保证流程上的扩展,或者就是我今天要说的这个解决方案:

依托程序,让它自己找到不同流程下的某个节点应该执行的操作。

以这种设计理念为前提的话,考验的就是我们或许已经遗忘了很久的JAVA基本功了。我又回忆起了年轻的时候,那些加班同时也是磨练技术的时光,那时我们面对的其实在今天看来是那么青涩的OA需求。

今天我就用Java最直接的Annotation,以及反射将这个问题解决。

我们首先来细化一下:

这个问题如何解决才能让我们在有一个合适的扩展性的同时代码最少。流程可以添加,如果增加了一个流程我们就需要添加一套处理逻辑,包含流程中各个节点的处理。节点的处理其实就是执行一个方法,方法中实现业务逻辑。一个公司中的流程其实大体上是一致的,这是一个潜移默化的定律,这跟老板的经营理念有关,比如财务要请假和技术要请假遵循的流程都是要找直属经理签字,审批,如果请假周期长还要请上一级的领导签字,然后提到HR处进行销假。

即使是互不相关的业务线也会出现可以提炼出来的基础流程,比如都需要审批,都需要提给HR或者某个行政主管去盖章等等。

所以回到我们的主旨:依托程序,让它自己找到不同流程下的某个节点应该执行的操作。实际的情况使我们能够实现这个主旨,同时也会利用Annotation和反射给我们的实现代码提供便捷。

初步思想:
我们可以设计一套基础流程,这套基础流程可以自动流转下去,不考虑分支。同时利用反射可以找到不同业务流程对应的具体业务类,在其中实现特殊节点的处理,比如分支的处理。

我们将问题简单化:
假设:我们有一条记录,给记录包含所有我们需要进行业务更新的字段。对于每个节点来讲,我们在每个节点的业务处理就是更新这条记录的状态。
所以我们可以这样设计流程:整个流程是通过该记录的状态来判断下一步需要流转到哪个角色,然后这个角色再更新必要的信息,提交后再更新这条记录的状态,然后流转到下一个流程,以此类推。

角色我们可以预先提出来,因为角色可以跟节点绑定,我们不涉及会签,所以可以断言,特定节点是由特定角色处理的,至于如何在一个列表中控制该角色下、不同节点上、可做的操作不同,是可以通过节点跟操作绑定来实现的。
比如:
有三个角色:HR 技术经理 和 技术人员
流程:请假流程
节点:编写请假单,请假单等待审批,请假单已审批(不考虑否定线),等待HR确认,HR已确认
操作:
技术人员的列表,每条记录后面的操作就可以是:编写请假单,提交,查看
HR的列表,就可以是:等待确认,提交,查看
技术经理的列表,就可以是:未审批,提交,查看
(之所以把“提交”单独出来是因为可以保证在此节点上要做的操作可以保存,不是一提交页面就触发下一个节点。)
因此,可以将上面的角色-节点-操作的对应关系跟记录本身的状态绑定,因为状态即节点(前面说过每个节点就是更新相应的状态),前台利用JS控制操作按钮的显示。

角色以及相关的对应关系提出来了以后,我们可以回到流程的设计上。

我们可以实现一个基础流程,换句话说,如果没有节点需要特殊处理,(比如走到某一个节点以后有分支,给不同的角色后对应不同的分支,然后决定以后要走不同的节点)那么直接走一个父类,执行的逻辑就是处理最简单的流程。
如果有特殊处理,那我们就用java的注解加反射去找到实现特殊处理节点方法的类,然后去执行。

贴出代码:

/**     * 通用处理流程方法     * @param baseState 这是一个空接口,等待传入实现类,实现类中需要写前面提到的特殊性节点的处理方法     * @param parameterObject 入参作为一个通用的实体,可以设置各种值的实体     * @return     * @throws Exception     */    public ParameterModel handleNextStep1(IBaseState baseState, ParameterObject parameterObject) throws Exception {        // 1.通过“状态”去“业务处理类”过滤出注解值是这个状态的方法。        /*         *  StatusType 实体的说明         *  private String statusName;          // 类型名称         *  private RoleEnum role;              // 对应的角色         *  private StatusTypeEnum statusType;  // 节点类型         *  private String preStatus;           // 上一个节点          */        List<StatusType> statusTypes = parameterObject.getStatusTypes();        if (parameterObject != null && parameterObject.getServiceBean() != null) {            // 此处获取的是数据当前所处的处理状态            if (StringUtils.isNotEmpty(parameterObject.getServiceBean().getCurrentStatus())) {                // 记录存在当前状态的值                // serviceBean对应的是你处理的业务记录,可能是请假记录或者需要审批的财务记录等等                ServiceBean serviceBean = parameterObject.getServiceBean();                // 此处获取的是实现类中特殊的处理方法                Method[] methods = baseState.getClass().getMethods();                boolean isRunMethod = false;// 是否调用了特殊处理方法                for (Method method : methods) {                    /*                     *  这里只有一个Annotation类,可以有多个,那样就需要用循环去特殊处理了,                     *  在此我们简化问题                     *  获取多个注解类有对应的方法 method.getAnnotations() 返回数组                     *                       *  @Retention(RetentionPolicy.RUNTIME)                        @Target({ElementType.METHOD})                        public @interface StateAnnotation {                            String name();                        }                     */                    StateAnnotation stateAnnotation = method.getAnnotation(StateAnnotation.class);                    if (stateAnnotation != null) {                        /*                         * 以下都是一些反射的内容                         * 目的就是获取实现类中某步流程中节点需要特殊处理的注解方法,然后执行                          */                        if (stateAnnotation.name().equals(serviceBean.getCurrentStatus())) {                            Class<?>[] parameters = method.getParameterTypes();                            if (parameters.length != 1) {                                throw new Exception("参数个数不匹配");                            }                            if (ParameterObject.class.isAssignableFrom(parameters[0])) {                                StatusType statusType = (StatusType) method.invoke(baseState, parameterObject);                                if(!parameterObject.isFlag()) {                                    throw new Exception(parameterObject.getErrorMessage());                                }                                /*                                 *  setStatusType方法中更新下一个节点的值以及记录的其他必要信息的更新,如当前状态更新时间等                                 *  在此可以update数据库记录                                  */                                setStatusType(statusType, parameterObject);                                isRunMethod = true;                                break;                            } else {                                throw new Exception("参数类型不匹配");                            }                        }                    }                    if (isRunMethod) {                        break;                    }                }                // 如果没有运行特性方法                /*                  * 这里就是最基础流程的处理,即没有分支等需要特殊处理的流程,直接走这个if就可以一直走到尾,                 * 比如你只有提交请假单,审批,通过三步,那你任何代码都不用编写直接执行这个方法,就可以走完流程                 * 前提是你要设置好你的StatusType                 *                  */                if (!isRunMethod) {                    String currentStatus = serviceBean.getCurrentStatus();                    for (StatusType statusType : statusTypes) {                        if (currentStatus.equals(statusType.getPreState())) {                            setStatusType(statusType, parameterObject);                            break;                        }                    }                }            }else{                // 处理开始节点,开始节点没有上一个节点,所以单独处理                setStatusType(parameterObject.getBusiness().getStartNode(), parameterObject);            }        } else {            throw new Exception("未找到数据");        }        return parameterObject;        }

以上是我写的主干代码,在此提供一个大致的思路,主要是给大家提个醒,在类似多流程处理的时候,可以利用反射、注解和工厂方法来解决,同时也可以防止流程节点的改变所产生的代码修改,详细的设计就不在此啰嗦了,因为这个受众群小,但是里面包含的思路,还是可以给大家借鉴的。如果大家有不清楚或者需要细化的问题,可以给我留言,给大家解决。

0 0
原创粉丝点击