暂时未有相关云产品技术能力~
参考:https://yuque.antfin-inc.com/hanjun.hw/yvmten2/ido5af引擎相关技术文档流程引擎到平台的mq消息链路https://yuque.antfin-inc.com/hanjun.hw/yvmten2/ido5af引擎核心介绍https://yuque.antfin-inc.com/alibpms/ssd5fr/bnn3qt加签的基础逻辑https://yuque.antfin-inc.com/alibpms/alipmc/append执行任务结束如何触发下一个任务序列https://yuque.antfin-inc.com/hanjun.hw/yvmten2/pi25gh如何解析取人规则https://yuque.antfin-inc.com/hanjun.hw/yvmten2/pi25gh什么时候激发下一个taskGrouphttps://yuque.antfin-inc.com/hanjun.hw/hqdpat5/evis8a 总览孕育:这一步是生命的起源,对应"任务"就是:申办人因为办理业务而发起一个流程。这是任务产生的摇篮。任务的使命就是为了完成业务;生产:这是新生命产生的过程,对应"任务"就是:任务的创建和激活。长大:婴儿长大了要办满月酒,办周岁,让亲人朋友分享喜悦。对应"任务"就是:通知审批人。进入社会:小孩长大了,迟早要进入社会,面对不同人的看法和检阅。对应"任务"就是:给审批人审阅,确认填写资料是否正确,是否合规。经受考验:人进入社会之后,会经受各种考验,如果能够成功顶住,就能获取飞跃般的成长。对应"任务"就是:合格合规,得到审批人的认可,不用被打回去重填。过关斩将:人在考验中不断成长,日积月累,最终会进入更高一个阶段。 对应"任务"就是:审批人点击【同意】,任务进入下一个阶段。死得其所:人一辈子修己,完成自己的使命,最后归于黄土,是一种圆满。 对应"任务"就是:从"RUNNING"状态进入"COMPLETED"状态,光荣完成自己的使命。名词解释审批任务需要人进行审批的任务,以下简称"任务"任务的状态 /** * 非激活任务 */ NEW, /** * 待办任务 */ RUNNING, /** * 暂停任务 */ PAUSED, /** * 取消/撤销 */ CANCELED, /** * 完成 */ COMPLETED, /** * 转交 */ REDIRECTED问题:转交之后原任务的状态是什么?TaskStatus.CANCELED审批消息MQ消息类型(此处指业务类型)说明(只列举部分)消息种类说明备注PROC_INST_START流程实例启动(发起流程实例) TASK_BATCH_ACTIVATED任务激活 TASK_COMPLETED任务完成 TASK_CANCEL任务取消 TASK_REDIRECTED任务转交 PROCESS_INSTANCE_TERMINATE流程实例终止(比如申请人撤销) PROC_INST_FINISH流程实例完成 PROCESS_INSTANCE_PAUSED流程实例暂停 生命周期01 生成流程实例这是任务产生的摇篮,没有流程实例,就不可能有任务。流程实例和任务是什么关系呢?一般一个流程实例有多个审批任务什么时候会生成流程实例呢?一般由业务活动触发比如上图,在采购招投标 的过程中,有很多业务活动,标号(1)到(4)的业务活动都会触发流程实例的生成,即发起审批流。流程实例生成之后,一定会生成任务吗?不一定。比如下面的流程,只有一个"自动节点":不会生成审批任务。必须要有人工节点(下图标号1-3),才能生成审批任务:(图01-03)发起实例流程(图01-04)下面是创建流程实例发起的调度项,然后把调度项存储到数据库,再由调度引擎进行一步的调度:ProcessInstanceStartResumptionExtension extension = new ProcessInstanceStartResumptionExtension(processInstance.getProcessId(), this.getGroupType()); extension.setParentProcessInstanceId(processInstance.getParentProcessInstanceId()); extension.setActivityId(activityId); ResumptionDO resumption = new ResumptionDO(processInstance.getProcessInstanceId(), extension, processInstance.getAppKey()); this.flowEngine.asyncStartFlowByResumption(resumption);asyncStartFlowByResumption 里面做了什么呢?其实就是把调度项插入到数据库 **this**.resumptionRepositoryManager.insert(resumption, **false**); 在调度项处理程序中调用WorkflowRunnerImpl 发起流程public void startWorkflow(String processInstanceId, long processId) { WorkflowContext context = createWorkflowContext(processId, processInstanceId); context.getWorkflow().start(context); }会从流程根节点找到第一个节点开始执行,首先找到的肯定是开始节点(图01-05)执行节点时,根据节点的类型,执行不同的Handler:执行HumanActivityHandler(人工节点handle) 才能生成审批任务发起流程实例之后,会发送一个变量更新的MQ消息:VariableChangeEvent02 生成新任务实例发起之后一定会生成任务吗?只有人工节点才能生成审批任务生成任务需要哪些准备工作?在流程设计的时候,需要为每个人工节点 设置好业务规则(取人规则);如果配置了条件节点,那么只有条件节点在运行态满足条件时才能走到审批节点;发起实例,即填表(如下图)(图01-06)HumanActivityHandler(人工节点) 的执行逻辑标号1:Activity activity = context.getFlowDefineModel().getActivityById(transition.getDestinationActivityId())DefaultTransitionRouteHandler.externalExecute标号2:ElementRuntimeManagerImpl.start 主要是检查前置拦截器,例如人工节点的前置条件com.alibaba.flowengine.ext.workflow.impl.ElementRuntimeManagerImpl#start 中主要是校验前置规则,实际调用handleBeforeInterceptors其实这是开始节点,因为流程实例启动时,是从开始节点开始执行的.其实目前activity.getActivitySetting().getBeforeInterceptors() 是预留的,还没有使用到基本this.check(elementToStart, context)都会直接返回true标号3:ElementRuntimeManagerImpl.enter 执行_节点进入后的行为_标号4:AbstractActivityBehavior.enter实际会调用BpmActivityBehavior.executeActivity然后根据activity.getActivitySetting().getIdentifyKey()(HUMAN) 获取到对应的handler,h获取到HumanActivityHandler标号5:获取节点实例并执行BpmsActivityEventBuilder.createActivityStartEvent 的任务:发送节点实例启动ActivityInstanceStartEvent的 MQ消息同时执行HumanActivityHandler.perform(activityContext)标号6:HumanActivityHandler.perform();执行节点实例有两种方式:(1)同步(2)异步:先作为调度项ResumptionDO,存储到数据库,然后由调度引擎来异步的执行标号7:_异步调度_;这里要注意的是:此时并没有真正干活,而是把真正要干活的任务存储到了调度项。下面是异步的方式获取刚才存进去的调度项,然后真正执行:(调度引擎是如何一步一步来执行调度任务(这里只执行人工节点实例)的)说明:标号1:这里调度引擎开始调度后,真正干活了,所以叫ItemWorker;标号2:completeResumptionSuccessed 方法名称感觉不太好,其实这里是真正干活了,即通过调度项找到具体的handler 去执行;其中会调用 resumptionHandler.handle(resumption)标号3:com.alibaba.workflowengine.adapter.resumption.handler.HumanStartResumptionHandler#handle 会做两件事;(a)通过调度项里面的扩展信息ExtensionContext 获取节点id (b)查询流程版本schema获取该人工节点的配置信息标号4:taskSequenceManager.initAndActivateRootTaskSequenceInternational是初始化人工节点,并激活;标号5:;标号6:激活任务序列;HumanStartResumptionHandler.handletaskSequenceManager.initAndActivateRootTaskSequenceInternationalinitAndActivateRootTaskSequenceInternational 具体做了什么呢?03 激活任务激活任务的时机是什么时候?一般任务在初始化之后,就会被激活任务为什么还需要一个激活的动作、多此一举?比如我收到一个审批任务时,我可能不太肯定,所以前加签给另外一个伙伴,那么我的原来审批任务的状态就变为(PAUSED),等前加签执行完之后,我的审批任务恢复为"激活"状态(RUNNING)激活任务之后还需要发MQ消息收到任务激活消息后做了什么处理?此处是针对消息类型 为TASK_BATCH_ACTIVATED的 消息进行处理:说明:标号1:MQ消费者 监听到消息之后进行处理;标号2:根据消息种类,转交给具体的handler 进行业务处理;标号3:根据消息种类,转交给具体的handler 进行业务处理;标号4:调用 发送审批通知 的逻辑;标号5:根据"任务激活"消息,获取新激活的任务信息,然后发送审批消息(支持公版钉钉和专有钉钉);标号6:组装消息卡片,调用钉钉open API 发送工作通知(审批消息);取消任务04 审批环节审批人收到任务之后,能做哪些事情?如果审批人发现自己处理不了应该怎么办?目前有两种方式:转交:把审批任务转交给其他人处理,自己原先的任务就会结束掉多人填表审批任务一定需要人工审批吗?不一定,(see https://bpms.alibaba-inc.com/processdesigner/newProcDesign?processId=15880317601)如果我们在人工节点 设置了"自动完成规则",那么系统会自动执行(同意或拒绝)该任务05 审批操作当审批人点击【同意】、【已处理】或【通过】(按钮名称是可以修改的)时,任务发生了什么变化?调用接口:executeTask,06 任务状态变化任务的状态机是怎样的?一个任务的状态变化会影响其他任务的状态吗?07 为什么有任务组任务组存在的核心目的是 支持审批节点的完成策略,上面的人工节点(审批节点) 配置了两个审批人,那么到底怎么样算审批通过呢?是其中一个人审批通过 就算审批通过(规则1),还是所有审批人都通过才算审批通过(规则2)呢、这就是完成策略。(以上面为例)实际会生成两个任务,这两个任务输入同一个 任务组(TaskGroup)比如采用规则1,那么只要有一个人审批通过,那么直接把所在任务组(TaskGroup) 置为“完成",另外一个任务就会自动取消掉。任务模型任务的taskType为MIDDLE_IN_ONE_BY_ONE 或 MIDDLE_IN_ALL_AT_ONCE同时分配(捞单)MIDDLE_IN_ALL_AT_ONCE逐级审批MIDDLE_IN_ONE_BY_ONE08 任务完成审批人点击审批页下面的【同意】、【已处理】或【通过】,如果不出意外,那么任务的状态就变为已完成.09 激活下一个任务执行完当前任务之后,如何激活下一个任务呢?下面从标号14的地方说起:com.alibaba.flowengine.ext.route.handler.impl.MultipleTransitionRouteHandler#externalExecute找到当前节点的出线,执行线规则;找到出线的startElement,按照前面的步骤执行 startElementz执行开始节点流程实例启动之后,第一个执行的就是开始节点实例也是通过 com.alibaba.flowengine.ext.workflow.impl.ElementRuntimeManagerImpl#start来执行的.详细步骤:开始节点的结构检查节点的前置规则(目前都为空,这套规则没有用起来),最终调用 handleBeforeInterceptors判断能够进入节点,最终调用executeActivity 方法;发送MQ事件:ActivityInstanceStartEvent调用StartActivityHandler .perform,直接返回true其实这里可以考虑用来实现流程发起的白名单机制;判断节点此时能够直接离开(完结)--通过com.alibaba.flowengine.ext.workflow.impl.ElementRuntimeManagerImpl#leave;实际是校验后置拦截器 ,调用方法handleAfterInterceptors,基本activitySetting.getAfterInterceptors() 都为空(此处没有用起来)如果可以离开,则执行this.countReduction,做减数;获取路由调度器RouteHandler 进行路由,因为当前节点是开始节点,所以获取到的路由器是线规则路由(MultipleTransitionRouteHandler)z最终会执行 MultipleTransitionRouteHandler..execute(element, context);获取开始节点的出线:Set<Transition> outgoingTransitions = activity.getOutTransitions()通过检查mei每个出线的线规则,找到符合要求的出线,如果检查出线呢?通过 com.alibaba.flowengine.ext.transition.behavior.AbstractTransitionBehavior#evaluate ,elementRuntimeManager.check判断能够进入出线 : this.elementRuntimeManager.enter(transition, context)最终执行完路由,找到出线的出口节点transition.getDestinationActivityId(),即下一个人工节点;然后又回到执行 com.alibaba.flowengine.ext.workflow.impl.ElementRuntimeManagerImpl#start,此处其实是回到了步骤2,又开始下一轮循环这里应该有一个误区,以为真个过程都是同步的.其实不是的.上面是开始节点的执行,下面我们开始执行人工节点,其实人工节点的执行就是异步的.人工节点的执行到底干了啥?校验 activity.getActivitySetting().getBeforeInterceptors();通过 activity.getActivitySetting().getIdentifyKey() 获取到具体的handler来执行,如果是人工节点,那么handler就是HumanActivityHandler;HumanActivityHandler 在执行过程中也分为异步和同步.如果是异步,则先把执行任务封装成为调度项存储到数据库;调度项异步调度执行思考目前工作流的性能瓶颈在 发起流程实例, 因为大事务,频繁的锁流程实例导致并发能力不够高;后续会优化的点有以下几点:梳理整个流程流转/任务流转的状态机;区分哪些是核心信息,哪些是非核心,如果是非核心信息,可以把一致性降低到最终一致性,提高性能;后续的计划流程实例发起白名单,可以通过人工加节点activity.getActivitySetting().getBeforeInterceptors() 来实现和OA审批打通;自动节点能力抽取,复用到更大的业务场景;
模型图 涉及的角色及说明 何为有限状态机有限状态机在维基百科中的解释是:有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。咋一看好像很虚幻,我们先看一下地铁运营的例子:站在有限状态机的角度来看,可以抽象如下几个关键点:状态(State)即地铁所处的状态,同上述例子的:“行进中”,“到站-开门”,“到站-关门”。事件(Event)即地铁都是在触发了某个事件才往下更改状态的,如:“行进中”触发了“刹车”事件才变成“到站-关门”的。动作(Transition)即地铁流转过程中具体的业务逻辑,如:“到站-关门”触发“启动”事件变成“行进中”,这中间可能需要发送出站通知,播放广播等操作。 示例说明 地铁的状态流转 类图 状态 public class SubwayState { /** * 状态编码 */ @Getter private StateCodeEnums stateCode; /** * 当前状态下可允许执行的动作 */ @Getter private List<SubwayTransition> transitions = new ArrayList<>(); public SubwayState(StateCodeEnums stateCode, SubwayTransition... transitions) { this.stateCode = stateCode; for (SubwayTransition transition : transitions) { this.transitions.add(transition); } } // 添加动作 public void addTransition(SubwayTransition transition) { transitions.add(transition); } @Override public String toString() { return stateCode.getDisplayName(); } } 状态枚举类: /** * 类描述: 状态. <br /> * * @author hanjun.hw * @since 2018/9/29 */ public enum StateCodeEnums implements IEnum { /** * 管理员 */ MOVING("MOVING", "行进中"), /** * 普通用户 */ CLOSED("CLOSED", "到站-关门"), OPEN("OPEN", "到站-开门"), SUSPENDED("SUSPENDED", "停运的"); private String code; private String displayName; StateCodeEnums(String code, String displayName) { this.code = code; this.displayName = displayName; } @Override public String getCode() { return code; } @Override public String getDisplayName() { return displayName; } @Override public String toString() { return displayName; } } 每个状态拥有不同的动作集合 状态-行进中:[刹车],状态-关门:[开门,启动],状态-开门:[关门],状态-停运:[启动],这个关联关系是在哪里定义的呢?在状态机初始化时创建说明:状态A 有三种动作,每个动作都是由某个具体事件触发,一个事件只能触发一个动作 事件 public class SubwayEvent { /** * 事件标识(编码) */ @Getter private EventCodeEnums eventCode; /** * 附属的业务参数 */ @Getter @Setter private Map<Object, Object> attributes = null; public SubwayEvent(EventCodeEnums eventCode) { this.eventCode = eventCode; } public SubwayEvent(EventCodeEnums eventCode, Map<Object, Object> attributes) { this.eventCode = eventCode; this.attributes = attributes; } @Override public String toString() { return eventCode.getCode(); } } 事件枚举 /** * 类描述: 事件类型. <br /> * * @author hanjun.hw * @since 2018/9/29 */ public enum EventCodeEnums implements IEnum { /** * 管理员 */ START_UP("START_UP", "启动"), /** * 普通用户 */ CLOSING("CLOSING", "关门"), OPENING("OPENING", "开门"), BRAKING("BRAKING", "刹车"); private String code; private String displayName; EventCodeEnums(String code, String displayName) { this.code = code; this.displayName = displayName; } @Override public String getCode() { return code; } @Override public String getDisplayName() { return displayName; } @Override public String toString() { return displayName; } } 事件和动作的关系 事件和动作是一一对应的。每个事件会触发特定的动作 动作 public abstract class SubwayTransition { /** * 触发事件 */ @Getter private EventCodeEnums eventCode; /** * 触发当前状态 */ @Getter private SubwayState currState; /** * 触发后状态 */ @Getter private SubwayState nextState; public SubwayTransition(EventCodeEnums eventCode, SubwayState currState, SubwayState nextState) { super(); this.eventCode = eventCode; this.currState = currState; this.nextState = nextState; } /** * 执行动作 * * @param event * @return * @author 张振伟 */ public SubwayState execute(SubwayEvent event) { System.out.println(String.format("当前是:%s 状态,执行:%s 操作后,流转成:\"%s\" 状态。", currState, eventCode, nextState)); if (this.doExecute(event)) { return this.nextState; } else { return null; } } /** * 执行动作的具体业务 * * @param event * @return * @author 张振伟 */ protected abstract boolean doExecute(SubwayEvent event); } 状态机 抽象类 public abstract class SubwayAbsStateMachine { /** * 定义的所有状态 */ private static List<SubwayState> allStates = null; /** * 状态机执行事件 * * @param stateCode * @param event * @return */ public SubwayState execute(StateCodeEnums stateCode, SubwayEvent event) { SubwayState startState = this.getState(stateCode); for (SubwayTransition transition : startState.getTransitions()) { if (event.getEventCode().equals(transition.getEventCode())) { return transition.execute(event); } } log.error("StateMachine[{}] Can not find transition for stateId[{}] eventCode[{}]", this.getClass().getSimpleName(), stateCode, event.getEventCode()); System.out.println(String.format("StateMachine[%s] Can not find transition for current state:[%s] eventCode:[%s]", this.getClass().getSimpleName(), stateCode, event.getEventCode())); return null; } public SubwayState getState(StateCodeEnums stateCode) { if (allStates == null) { log.info("StateMachine declareAllStates"); allStates = this.declareAllStates(); } for (SubwayState state : allStates) { if (state.getStateCode().equals(stateCode)) { return state; } } return null; } /** * 由具体的状态机定义所有状态 * * @return * @author 张振伟 */ public abstract List<SubwayState> declareAllStates(); } 实现类 public class SubwayStateMachine extends SubwayAbsStateMachine { @Override public List<SubwayState> declareAllStates() { // 定义状态机的状态 List<SubwayState> stateList = new ArrayList<>(); SubwayState movingState = new SubwayState(StateCodeEnums.MOVING); SubwayState closedState = new SubwayState(StateCodeEnums.CLOSED); SubwayState openState = new SubwayState(StateCodeEnums.OPEN); SubwayState suspensionState = new SubwayState(StateCodeEnums.SUSPENDED); movingState.addTransition(new BrakeTransition(movingState, closedState)); closedState.addTransition(new StartupTransition(closedState, movingState)); closedState.addTransition(new OpenTransition(closedState, openState)); openState.addTransition(new CloseTransition(openState, closedState)); suspensionState.addTransition(new StartupTransition(suspensionState, movingState)); stateList.add(movingState); stateList.add(closedState); stateList.add(openState); stateList.add(suspensionState); return stateList; } /** * 定义“刹车”动作 */ public class BrakeTransition extends SubwayTransition { public BrakeTransition(SubwayState currState, SubwayState nextState) { super(EventCodeEnums.BRAKING, currState, nextState); } @Override protected boolean doExecute(SubwayEvent event) { System.out.println("执行刹车操作..."); return true; } } /** * 定义“启动”动作 */ public class StartupTransition extends SubwayTransition { public StartupTransition(SubwayState currState, SubwayState nextState) { super(EventCodeEnums.START_UP, currState, nextState); } @Override protected boolean doExecute(SubwayEvent event) { System.out.println("执行启动操作..."); return true; } } /** * 关门 */ public class CloseTransition extends SubwayTransition { public CloseTransition(SubwayState currState, SubwayState nextState) { super(EventCodeEnums.CLOSING, currState, nextState); } @Override protected boolean doExecute(SubwayEvent event) { System.out.println("执行关门操作..."); return true; } } /** * 开门 */ public class OpenTransition extends SubwayTransition { public OpenTransition(SubwayState currState, SubwayState nextState) { super(EventCodeEnums.OPENING, currState, nextState); } @Override protected boolean doExecute(SubwayEvent event) { System.out.println("执行开门操作..."); return true; } } } 状态机实现类做了哪些事情? 定义所有的状态(有限个状态); 每个状态的动作集合; 每个动作的定义:(a)由什么事件触发?(b)状态的流转(从源状态到目标状态) 测试类 public class TestSubwayStateMachine { @Test public void test() { SubwayAbsStateMachine sm = new SubwayStateMachine(); SubwayState state = sm.execute(StateCodeEnums.MOVING, new SubwayEvent(EventCodeEnums.BRAKING)); } } 思考&借鉴 状态机模式的前提是 有限个状态,不适用无线个状态的场景; 每个状态都有特定的动作集合;动作是由事件触发的; 可以把状态机模式和观察者模式进行比较,也是以事件驱动的,SubwayTransition 可以看做监听程序,每个事件都有注册事件监听程序 借鉴 什么场景适合使用状态机模式? 有静态的状态,并且是有限的; 业务逻辑围绕 不同状态之间的流转切换来实现; 状态之间的切换 往往通过不同的事件来触发(驱动) 举例:a. 银行账户状态正常,锁定,冻结b. 电脑的状态开启,待机,关机,锁屏c. 房源的状态看房中,验房中,待签约,已签约,待评价等
gradle 是什么 gradle 是一个让构建自动化的工具,类似于maven,ant的功能. 使用gradle可以给java项目编译,单元测试,打包,或者生成可执行的jar包等 gradle的依赖环境 gradle依赖java环境,所以使用gradle前需要安装jdk 或jre gradle 构建项目的流程 gradle的构建依赖于task,task可以指定与其他task之间的依赖关系比如,有两个task,walk 和bike,如果指定walk依赖bike,那么执行walk前会先执行bike. task的来源有两种: 插件提供,gradle有很多现成的插件; 自定义:在build.gradle 文件中声明task 生命周期 maven 的构建是通过生命周期来排列的,而 gradle 不是,它没有生命周期,它是通过任务的依赖关系来组织顺序的。 build.gradle 的执行流程 LifecycleThere is a one-to-one relationship between a Project and a build.gradle file. During build initialisation, Gradle assembles a Project object for each project which is to participate in the build, as follows: Create a Settings instance for the build. Evaluate the settings.gradle script, if present, against the Settings object to configure it. Use the configured Settings object to create the hierarchy of Project instances. Finally, evaluate each Project by executing its build.gradle file, if present, against the project. The projects are evaluated in breadth-wise order, such that a project is evaluated before its child projects. This order can be overridden by calling Project.evaluationDependsOnChildren() or by adding an explicit evaluation dependency using Project.evaluationDependsOn(java.lang.String). 如何执行任务 方式一:使用全局命令 gradle <任务名> 方式二:使用项目中的脚本 ./gradlew <任务名> task 能实现哪些功能 下面是一些常用的基本功能 复制 task walk(description:'walk') { doLast { println 'walk...' println myName copy { into 'demo' exclude '**/.svn/**' from('README.md') } } } 删除 task walk(description:'walk') { doLast { println 'walk...' println myName project.delete { delete 'README.md' followSymlinks = true } } } 生成源码 jar 包 task sourcesJar(type: Jar) { classifier = 'sources' from sourceSets.main.allSource doFirst { manifest = defaultManifest([ project : subproj, projectVendor: project_vendor ]) } } 设置/添加依赖 使用project 下面的dependencies see /Users/whuanghkl/.gradle/wrapper/dists/gradle-4.4-all/9br9xq1tocpiv8o6njlyu5op1/gradle-4.4/src/core-api/org/gradle/api/Project.java void dependencies(Closure configureClosure); dependencies 方法的参数是一个配置闭包,那么这个闭包有哪些配置呢? maven 中的 <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>2.6</version> <scope>compile</scope> </dependency> 等价于 compile 'commons-lang:commons-lang:2.6' 参考:https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations https://docs.gradle.org/current/userguide/managing_dependency_configurations.html 引入本地jar包 compile project.fileTree(dir:'/Users/whuanghkl/code/mygit/myproject/target',include:['io0007-0.0.1.jar']) 依赖指定文件夹 compile project.fileTree(dir:'/Users/xx/Documents/mygit/demo/io0007/target',include:['*.jar']) compile project.fileTree(dir:'/Users/xx/Documents/mygit/demo/idea_plugin/intellij-aa-editor/lib',include:['*.jar']) 如何查询依赖的版本 直接使用 maven 仓库,那么就可以直接查询maven 仓库 :https://mvnrepository.com/search?q=common-lang gradle中依赖的仓库有多种: 参考:https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.RepositoryHandler.html 我们可以选择 maven仓库: repositories { mavenCentral() } 也可以使用本地的 maven 仓库: repositories { mavenLocal() } 甚至可以直接指定依赖的 jar 包目录: maven { url "lib" } 那么 查询依赖就和maven一样了 . gradle的插件有哪些 参考 https://docs.gradle.org/current/userguide/userguide.html springboot 项目生成可执行的jar包 我的项目是spring boot,所以需要引入插件'org.springframework.boot' id 'org.springframework.boot' version '2.0.4.RELEASE' 需要在build.gradle 文件中 指定可执行jar的main class : jar { manifest { attributes 'Main-Class': 'com.kunlunsoft.Application' } } 执行任务bootJar 就可以生成可执行的jar包 gradle 与maven相比有哪些优势 功能更强大,可以很方便的自定义任务; 添加依赖更简洁方便; 任务的执行流程更灵活,不像maven的生命周期那么固定. 可以编写 groovy 代码,我们知道 groovy 是非常灵活的。 参考 https://my.oschina.net/huangweiindex/blog/1844872 https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.dsl.DependencyHandler.html https://docs.gradle.org/current/userguide/java_plugin.html#tab:configurations 我项目中完整的build.gradle 文件如下: plugins { id 'java' id 'base' id 'org.springframework.boot' version '2.0.3.RELEASE' } group 'com.kunlunsoft' version '1.0.0-SNAPSHOT' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } jar { manifest { attributes 'Main-Class': 'com.kunlunsoft.Application' } } task walk(description:'walk') { doLast { println 'walk...' } } repositories { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' testCompile("org.springframework.boot:spring-boot-starter-test") //数据源 compile 'org.springframework.boot:spring-boot-starter:1.5.14.RELEASE' compile 'org.springframework.boot:spring-boot-starter-web:1.5.14.RELEASE' compile 'org.springframework.boot:spring-boot-starter-data-redis:1.5.14.RELEASE' compile 'mysql:mysql-connector-java:5.1.38' compile project.fileTree(dir:'/Users/whuanghkl/code/myproject/target',include:['io0007-0.0.1-SNAPSHOT.jar']) compile 'com.google.guava:guava:23.0-rc1' compile 'org.apache.commons:commons-email:1.5' compile 'org.codehaus.jackson:jackson-mapper-lgpl:1.9.12' //redis // compile 'org.springframework.data:spring-data-redis:1.8.13.RELEASE' compile 'redis.clients:jedis:2.9.0' compile 'org.springframework.statemachine:spring-statemachine-core:1.2.0.RELEASE' compile 'com.alibaba:fastjson:1.2.47' //配置mybatis compile "org.mybatis.spring.boot:mybatis-spring-boot-starter:1.1.1" compile 'org.springframework.boot:spring-boot-gradle-plugin:1.5.14.RELEASE' // compile 'org.springframework:springloaded:1.5.14.RELEASE' }
我的理解 很久之前有个老师给我们讲观察者模式时,举了一个例子:妈妈照顾婴儿,他们在不同的房间,婴儿在婴儿房间A,妈妈在书房M,为了保证婴儿睡醒之后能及时得到照顾,妈妈每隔10分钟就去房间A瞧瞧,看看婴儿是否醒了.妈妈为了防止忘记,给自己定了一个闹钟,每隔10分钟响一次. while (10分钟之后) { watchBaby();//检查婴儿是否睡醒 } 一段时间之后,妈妈觉得很累,而且效率很低,因为很多次妈妈去照看婴儿时,发现婴儿在睡觉啥事也没有. 后来,妈妈发现了一个好方法,在婴儿身上寄一个铃铛,婴儿只要一动,铃铛就会响,妈妈就能听到.这样妈妈就可以安心工作了,只要听到铃铛响就跑过去照看下. 关系图: 其实上面的例子涉及到两种模式: 妈妈每隔10分钟主动去查看 是轮询模式; 妈妈不用主动去检查,婴儿醒了,妈妈就会听到铃铛, 是观察者模式 那种模式效率更高呢?一般情况下,观察者模式效率更高,轮询,每隔一段时间去查看,浪费了资源. 观察者模式的角色 有如下角色:观察者,被观察者,事件,有如下操作:注册,通知 观察者 案例中的妈妈就是观察者,她观察婴儿,看它醒了没有,有什么需求 被观察者 婴儿是被观察者,是被关注的对象.一旦它发生什么事件,观察者就会有所响应 事件 观察者关注的事件就是铃铛是否响,铃铛一响说明婴儿醒了.婴儿一醒,妈妈就要去照顾,比如喂奶等. 注册(订阅) 妈妈在婴儿身上挂一个铃铛,就是注册事件.一旦该事件发生,观察者就会有所响应.一旦婴儿醒了,妈妈就会听到铃铛声(收到通知) 通知 发生事件(婴儿醒了)时,被观察者(婴儿)会主动通知观察者(妈妈) java 类图 示例 接口 观察者: /** * 接口描述: 观察者. <br /> * * @author hanjun.hw * @since 2018/11/6 */ public interface Observer { /** * 观察者收到通知后,观察者进行响应 * * @param observable */ void update(Observable observable); } 被观察者: public abstract class Observable { /** * 订阅观察者 * * @param observer */ public abstract void register(Observer observer); /** * 通知观察者 */ protected abstract void notifyAllObservers(); /** * 观察者真正感兴趣的事件 */ public void wakeUp() { bellRinging(); notifyAllObservers(); } /** * 观察者观察的事件 */ protected abstract void bellRinging(); } 观察者实现 /** * 类描述: 具体观察者:妈妈. <br /> * * @author hanjun.hw * @since 2018/11/6 */ public class Mother implements Observer { @Override public void update(Observable observable) { System.out.println("婴儿醒了,去照看 :" + observable); } } 被观察者实现 /** * 类描述: 被观察者:婴儿. <br /> * * @author hanjun.hw * @since 2018/11/6 */ public class Baby extends Observable { private HashSet<Observer> observers = new HashSet<>(); /** * 把铃铛系在婴儿身上 * @param observer */ @Override public void register(Observer observer) { observers.add(observer); } /** * 妈妈听到铃铛声,做出响应 */ @Override protected void notifyAllObservers() { observers.forEach(o -> o.update(this)); } @Override protected void bellRinging() { System.out.println("婴儿睡醒了 ,铃铛响了"); } } 测试 public static void main(String[] args) { Baby concreteObservable = new Baby(); Mother concreteObserver = new Mother(); concreteObservable.register(concreteObserver); concreteObservable.wakeUp(); } 应用场景 Google eventbus 组成部分 事件(什么类型的事件)---对应铃铛响了事件监听器,即事件处理程序(响应)----对应妈妈注册事件监听器(register);----对应往婴儿身上挂铃铛触发事件(trigger/post);---婴儿醒了,摇动了铃铛 实例 事件,可以是任何自定义对象 /** * Created by whuanghkl on 17/6/22.<br /> * 自定义事件 */ public class AccessLoggerEvent { } 事件监听器 /** * Created by whuanghkl on 17/6/22.<br /> * 事件监听器 */ @Component public class AccessLoggerListener { @Resource private EventBus eventBus; /** * 订阅 */ @PostConstruct public void init() { eventBus.register(this); } @Subscribe public void logEvent(AccessLoggerEvent event) { System.out.println("logEvent"); } } 事件监听器自己注册到eventBus 在控制器中触发事件 AccessLoggerEvent accessLoggerEvent = new AccessLoggerEvent(); eventBus.post(accessLoggerEvent); 婴儿是什么? 是上述代码(eventBus.post)所在类 java swing 按钮单击/文本框回车 JButton cancelButton = new JButton("Cancel"); cancelButton.setActionCommand("Cancel"); cancelButton.addActionListener(new ActionListener() {//ActionListener 是观察者 @Override public void actionPerformed(ActionEvent e) {//观察者的响应 LoginDialog.this.dispose(); } }); 涉及的元素:按钮 -----对应婴儿按钮增加事件 ,即调用 addActionListener() ---对应往婴儿身上挂铃铛用户点击按钮;---婴儿醒了,摇动了铃铛执行 addActionListener() 执行 addActionListener() 添加的ActionListener ---对应 妈妈听到铃声之后去照看妈妈是什么? 是ActionListener 对象 react 数据的双向绑定 参考:https://juejin.im/post/59f2e9b16fb9a04529360146 mq 消息队列订阅 多路复用机制epoll 也使用了观察者模式, 与之相对的select:整个socket集合会被遍历一次,比如集合里面有1024个socket,即便只有一个socket有数据可读,也遍历所有的1024个socket,这个是观察者模式的反例.epoll的实现逻辑就是观察者模式的思想:协议数据包到达网卡并被排入socket的接收队列。睡眠在socket的睡眠队列wait_entry被唤醒,wait_entry_sk的回调函数epoll_callback_sk被执行。epoll_callback_sk将当前socket插入epoll的ready_list中。这样就不用遍历整个socket队列了,而只需要遍历ready_list 观察者模式的作用 解耦,比如 Google eventbus提高响应速度(相对于轮询)一般是辅助的容易变化的业务 观察者模式和mq(发布订阅) 的区别 mq:削峰,被观察者和观察者不用感知对方的存在;观察者模式中,观察者和被观察者可以感知到对方的存在 思考 设计模式是经验的总结,是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结;观察者模式其实在日常生活中也有体现,比如银行业务 有短信通知,如果我绑定了手机号(订阅),那么银行卡有交易时(关注的事件发生),就会自动给我们发短信(响应) 既能把观察者模式理解成为一种思想,也能理解为解耦的一种策略
常规的登录 常规的是通过session步骤: 用户在浏览器登录 后台鉴权,若登录成功,则把用户信息写入session,servlet自动生成JSESSIONID 返回浏览器; 浏览器把JSESSIONID 写入cookie说明:cookie是浏览器的存储文件,存储的只是JSESSIONID,而不是用户信息; JSESSIONID 只是一个钩子,用户信息其实存储在服务器端. 比如我在浏览器登录之后,重新打开一个标签页,输入相同的地址,仍然是登录状态,因为标签页共享cookie.也就是说我只要获取到JSESSIONID ,就可以获取用户隐私信息,比如除用户名以外的其他信息(密码,姓名,年龄等).也可以做一些敏感操作,比如修改密码 浏览器发送请求时会带上cookie这样服务器端才知道是否登录过: 例如,我获取到JSESSIONID 之后,使用HTTP模拟发送请求: 服务器端是如何判断是否登录 /*** * 判断是否已登录 * @param user2 * @return */ public static boolean isLogined(User user2,String loginFlag){ if(ValueWidget.isNullOrEmpty(user2)||ValueWidget.isNullOrEmpty(user2.getUsername()) ||loginFlag == null ||( !loginFlag.equalsIgnoreCase(Constant2.FLAG_LOGIN_SUCCESS))){ return false; }else{ return true; } } /*** * 判断是否已登录 * @param session * @return */ public static boolean isLogined(HttpSession session){ String loginFlag = (String) session .getAttribute(Constant2.SESSION_KEY_LOGINED_FLAG); User user2 = (User) session.getAttribute(Constant2.SESSION_KEY_LOGINED_USER); return isLogined(user2,loginFlag); } 通过 cookie 实现单点登录 登录之前,会向服务器请求一个随机token,同时返回cookie,例如:CCC=63314c585041e889766fec6657879c; Expires=Mon, 21-Sep-2015 06:48:27 GMT; Path=/ 登录时带上这个cookie(即CCC=63314c585041e889766fec6657879c) 服务器鉴权,登录成功之后,把登录凭证和CCC 绑定,即服务器可以通过CCC 判断是否登录,也可以通过CCC 获取用户敏感信息. 这样的话,只要登录过,用户不主动清除cookie,那就一直是登录状态.如果用户主动清除了cookie,那就不是登录状态了 详细流程,逻辑(1)登录前,浏览器先向认证服务器请求一个OTP,认证服务器返回OTP,同时返回一个cookie给浏览器;(2)认证服务器把OTP 和CCC(cookie) 挂钩;(3)登录时连同带上OTP(4)若登录成功,则认证服务器返回登录凭证(access token)给浏览器;(5)认证服务器把CCC 和登录凭证挂钩;(6)通过CCC 可以判断用户是否有权限. 所以,(a)可以通过CCC 获取登录凭证(b)也可以通过OTP 获取登录凭证 根本原因:服务器把OTP 和CCC(cookie) 挂钩 那么CCC记在什么地方?浏览器 获取OTP的接口返回: 效果: A和B是同一个公司的应用,在同一个浏览器中登录了应用A ,就可以在该浏览器中直接访问应用B,而不用重新登录. 具体实现原理: 1,登录之前访问 认证系统的动态口令接口,获取动态口令:auth_token同时会返回cookie:CCC ,目的:把浏览器和 auth_token 挂钩等浏览器访问其他应用时,把该CCC带过去.2,登录时参数包括(1)中返回的auth_token ;3,访问应用B 时,浏览器会把cookie中的CCC传过去,应用B 拿到之后,给认证系统认证,判断是否登录;若认证通过,则返回用户信息 单点登录的目的 同一个公司的应用,只需要登录一次。比如我登录了阿里云,再打开淘宝 ,天猫或钉钉就不用重复登录(输入用户名和密码)。 具体场景(Web) 小明登录了 Web应用A,再进入Web应用B时,不用重新输入用户名和密码,就自动登录了。 问题:应用A和应用B要拥有相同的父域名吗? 不用!!!不用!!!不用!!!阿里云 和淘宝的域名就不一样 具体实现(基于 cookie) otp :one time password 就是一次一密 认证中心接口 接口一:jsonp接口,获取 otp请求方式:jsonp获取 otp,同时可以判断是否已登录http://auth.xxx.com/authentication/otp.jsonp?callbk=callbkMethod&clientId=应用的clientId 返回{ "isLogined":true, "otp":"433u23"} 注意:jsonp 接口只需要 clientId,不需要 secret接口二:使用 otp登录请求方式:posthttp://auth.xxx.com/authentication/login.json />参数:username:password:otp:接口一获取的 otpimgCode:图形验证码 返回:{ "access_token":"", "refresh_token":"", "timeout":12000 ...} 接口三:opt 换取 access_token请求方式:posthttp://auth.xxx.com/authentication/token.json?secret=应用的secret&clientId=应用的clientId&opt=433u23返回:{ "access_token":"", "refresh_token":"", "timeout":12000 ...} 认证中心的作用 判断是否已登录; otp 换access_token 未登录-登录流程 应用 A 已经登录,访问应用 B 时
epoll 是 Linux 下 IO多路复用的机制,可以监视多个描述符的读/写等事件,一旦某个描述符就绪(一般是读或者写事件发生了),就能够将发生的事件通知给关心的应用程序去处理该事件。 以前的网络编程方式 拿使用 socket 实现的聊天程序举例。服务器端: public static void main(String[] args) { ServerSocket server = null; try { server = new ServerSocket(PROT); System.out.println(" server start .. "); //进行阻塞 while (true) {//这里应该循环,使得可以接受多个客户端的请求。 Socket socket = server.accept();//会阻塞,直到有客户端来链接 //新建一个线程执行客户端的任务 new Thread(new ServerHandler(socket)).start(); } } catch (Exception e) { e.printStackTrace(); } finally { if (server != null) { try { server.close(); } catch (IOException e) { e.printStackTrace(); } } server = null; } } 每连接一个客户端,就新启动一个线程,如果有1万个客户端,就会产生一万个线程,会严重消耗掉 CPU 性能。当然可以使用线程池,但是无法根本性地解决问题 使用 Nio while (true) { try { //1 必须要让多路复用器开始监听 this.seletor.select(); //2 返回多路复用器已经选择的结果集 Iterator<SelectionKey> keys = this.seletor.selectedKeys().iterator(); //3 进行遍历 while (keys.hasNext()) { //4 获取一个选择的元素 SelectionKey key = keys.next(); //5 直接从容器中移除就可以了 keys.remove(); //6 如果是有效的 if (key.isValid()) { //7 如果为阻塞状态 if (key.isAcceptable()) { this.accept(key); } //8 如果为可读状态 if (key.isReadable()) { this.read(key); } //9 写数据 if (key.isWritable()) { this.write(key); //ssc } } } } catch (IOException e) { e.printStackTrace(); } } 如果有客户端连接成功: private void accept(SelectionKey key) { try { //1 获取服务通道 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //2 执行阻塞方法 SocketChannel sc = ssc.accept(); //3 设置阻塞模式 sc.configureBlocking(false); //4 注册到多路复用器上,并设置读取标识 sc.register(this.seletor, SelectionKey.OP_READ); } catch (IOException e) { e.printStackTrace(); } } 我们看到 始终只有一个线程,不管有多少个客户端来连接。注意:不是没有任何阻塞。seletor.select()就会阻塞,但是其他的读写事件都不会,不像传统的 inputStream.read() 就会卡死在那里,直到有数据可读。 Nio和传统 io 的区别 传统 io 每连接一个客户端,就会产生一个 socket,有多少个 socket 就会建立多少个线程; 判断 socket 是否可读或可写,需要我们程序自己轮询; 读写操作可能会阻塞直到可处理; 传统 socket 是面向流的。 Nio 一个线程就可以处理 n 个 socket得读写; 不需要轮询所有的 socket,只需要轮询 this.seletor.select() ; 面向缓冲区的。 为什么 Nio 不需要轮询所有的 socket 就知道哪些 socket 就绪(可读或可写)呢?因为在 Nio 中,任何 socket 就绪都会回调一个钩子方法,应用程序就会马上知道。 epoll 参考:http://man7.org/linux/man-pages/man7/epoll.7.html epoll 是对 poll 的增强epoll 提供了三个系统调用: epoll_create 创建一个 epoll 实例,也是一个文件描述符,所有后续调用到的epoll接口都会使用此文件描述符。 epoll_ctl epoll实例的操作接口方法签名:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);共有四个参数: 参数名 含义 epfd epoll 实例 op 操作类型,枚举:EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL, op为EPOLL_CTL_ADD 表示注册一个目标文件描述符 到 epoll 实例 fd 目标文件描述符 event 目标文件描述符感兴趣的事件,比如可读,可写,event 结构如下 events 是数字,可以是下面的枚举值由 or 组成的掩码:EPOLLIN:可读;EPOLLOUT:可写;EPOLLERR:有异常发生;等等,具体参考:http://man7.org/linux/man-pages/man7/epoll.7.html epoll_wait 等待 epoll 实例上的 io 事件发生。方法签名如下:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 参数名 含义 epfd epoll 实例 maxevents 返回的最大的可处理的事件数量,必须大于0 timeout epoll_wait 方法阻塞的超时时间 event 目标文件描述符待处理的事件,比如可读,可写 超时什么时候结束呢 任何一个文件描述符回调了事件(前面通过epoll_ctl 注册的事件); 被signal handler 中断; 超时 epoll 和 poll 的最大的区别(优点) 能监控更多的文件描述符; 不需要每次监控都要把所有的文件描述符 从用户态拷贝到内核态; 不需要每次遍历所有的文件描述符。 epoll为什么判断是否有可处理的事件时不用遍历所有的文件描述符 说白了,epoll 采用了事件回调机制(类似 [观察者模式]()),其实后面有很多框架都采用了这种事件回调机制,比如 Nodejs 等。epoll 监听 fd 事件时,有一个就绪队列,一旦某个 fd 就绪(即有待处理的事件,例如可读,可写),就会放在这个就绪队列,应用程序调用.select() 时,不用重新遍历所有的 fd,只需要查询这个就绪队列就行。 Nio select 源码分析 注册 channel(套接字) see /Users/xxx/Downloads/jdk_src2/sun/nio/ch/SelectorImpl.java protected final SelectionKey register(AbstractSelectableChannel ch, int ops, Object attachment) { if (!(ch instanceof SelChImpl)) throw new IllegalSelectorException(); SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this); k.attach(attachment); synchronized (publicKeys) { implRegister(k); } k.interestOps(ops); return k; } 其中, implRegister(k) 是为了写入 channel 文件描述符的位置; k.interestOps(ops) 为了写入监听的channel 可处理的操作 ops 的取值 SelectionKey.OP_CONNECT SelectionKey.OP_WRITE SelectionKey.OP_READ implRegister的实现 见/Users/whuanghkl/Downloads/rt.jar.source/classes/sun/nio/ch/AbstractPollSelectorImpl.java protected void implRegister(SelectionKeyImpl ski) { synchronized (closeLock) { if (closed) throw new ClosedSelectorException(); // Check to see if the array is large enough if (channelArray.length == totalChannels) { // Make a larger array int newSize = pollWrapper.totalChannels * 2; SelectionKeyImpl temp[] = new SelectionKeyImpl[newSize]; // Copy over for (int i=channelOffset; i<totalChannels; i++) temp[i] = channelArray[i]; channelArray = temp; // Grow the NativeObject poll array pollWrapper.grow(newSize); } channelArray[totalChannels] = ski; ski.setIndex(totalChannels); pollWrapper.addEntry(ski.channel); totalChannels++; keys.add(ski); } } void addEntry(SelChImpl var1) { this.putDescriptor(this.totalChannels, IOUtil.fdVal(var1.getFD())); this.putEventOps(this.totalChannels, 0); this.putReventOps(this.totalChannels, 0); ++this.totalChannels; } Windows系统 实现 见 /Users/xxx/Downloads/openjdk-8u40-src-b25-10_feb_2015/openjdk/jdk/src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java protected void implRegister(SelectionKeyImpl ski) { synchronized (closeLock) { if (pollWrapper == null) throw new ClosedSelectorException(); growIfNeeded(); channelArray[totalChannels] = ski; ski.setIndex(totalChannels); fdMap.put(ski); keys.add(ski); pollWrapper.addEntry(totalChannels, ski); totalChannels++; } } 重点方法: pollWrapper.addEntry(totalChannels, ski); void addEntry(SelChImpl var1) { this.putDescriptor(this.totalChannels, IOUtil.fdVal(var1.getFD())); this.putEventOps(this.totalChannels, 0); this.putReventOps(this.totalChannels, 0); ++this.totalChannels; } 注册(监听)channel感兴趣的操作 k.interestOps(ops) public SelectionKey interestOps(int ops) { ensureValid(); return nioInterestOps(ops); } public SelectionKey nioInterestOps(int ops) { if ((ops & ~channel().validOps()) != 0) throw new IllegalArgumentException(); channel.translateAndSetInterestOps(ops, this); interestOps = ops; return this; } 方法channel.translateAndSetInterestOps(ops, this)中调用了 void translateAndSetInterestOps(int ops, SelectionKeyImpl sk); translateAndSetInterestOps 见/Users/xxx/Downloads/jdk_src2/sun/nio/ch/SocketChannelImpl.java /** * Translates an interest operation set into a native poll event set */ public void translateAndSetInterestOps(int ops, SelectionKeyImpl sk) { int newOps = 0; if ((ops & SelectionKey.OP_READ) != 0) newOps |= Net.POLLIN; if ((ops & SelectionKey.OP_WRITE) != 0) newOps |= Net.POLLOUT; if ((ops & SelectionKey.OP_CONNECT) != 0) newOps |= Net.POLLCONN; sk.selector.putEventOps(sk, newOps); } /Users/xxx/Downloads/jdk_src2/sun/nio/ch/AbstractPollSelectorImpl.java 中 public void putEventOps(SelectionKeyImpl sk, int ops) { synchronized (closeLock) { if (closed) throw new ClosedSelectorException(); pollWrapper.putEventOps(sk.getIndex(), ops); } } void putEventOps(int i, int event) { int offset = SIZE_POLLFD * i + EVENT_OFFSET; pollArray.putShort(offset, (short)event); } unsafe 常用操作解析 putInt 表示在指定位置写入一个 int类型数据 /** * Writes an int at the specified offset from this native object's * base address. * * @param offset * The offset at which to write the int * * @param value * The int value to be written */ final void putInt(int offset, int value) { unsafe.putInt(offset + address, value); } poll 见 /Users/xxx/Downloads/openjdk-8u40-src-b25-10_feb_2015/openjdk/jdk/src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java private int poll() throws IOException{ // poll for the main thread return poll0(pollWrapper.pollArrayAddress, Math.min(totalChannels, MAX_SELECTABLE_FDS), readFds, writeFds, exceptFds, timeout); } private int poll(int index) throws IOException { // poll for helper threads return poll0(pollWrapper.pollArrayAddress + (pollArrayIndex * PollArrayWrapper.SIZE_POLLFD), Math.min(MAX_SELECTABLE_FDS, totalChannels - (index + 1) * MAX_SELECTABLE_FDS), readFds, writeFds, exceptFds, timeout); } 调用操作系统的能力来监听socket select select做了哪些事? poll,阻塞,获取channel 列表中可操作的channel; 如果有可以操作的channel,则poll 会返回; 根据操作系统调用的返回readFds, writeFds, exceptFds,来更新selectedKeys 见 /Users/xxx/Downloads/jdk_src2/sun/nio/ch/AbstractPollSelectorImpl.java /** * Copy the information in the pollfd structs into the opss * of the corresponding Channels. Add the ready keys to the * ready queue. */ protected int updateSelectedKeys() { int numKeysUpdated = 0; // Skip zeroth entry; it is for interrupts only for (int i=channelOffset; i<totalChannels; i++) { int rOps = pollWrapper.getReventOps(i); if (rOps != 0) { SelectionKeyImpl sk = channelArray[i]; pollWrapper.putReventOps(i, 0); if (selectedKeys.contains(sk)) { if (sk.channel.translateAndSetReadyOps(rOps, sk)) { numKeysUpdated++; } } else { sk.channel.translateAndSetReadyOps(rOps, sk); if ((sk.nioReadyOps() & sk.nioInterestOps()) != 0) { selectedKeys.add(sk); numKeysUpdated++; } } } } return numKeysUpdated; } 处理监听结果 见 /Users/xxx/Downloads/openjdk-8u40-src-b25-10_feb_2015/openjdk/jdk/src/windows/classes/sun/nio/ch/WindowsSelectorImpl.java private int processSelectedKeys(long updateCount) { int numKeysUpdated = 0; numKeysUpdated += processFDSet(updateCount, readFds, Net.POLLIN, false); numKeysUpdated += processFDSet(updateCount, writeFds, Net.POLLCONN | Net.POLLOUT, false); numKeysUpdated += processFDSet(updateCount, exceptFds, Net.POLLIN | Net.POLLCONN | Net.POLLOUT, true); return numKeysUpdated; } AbstractPollArrayWrapper 源码 /** * Manipulates a native array of pollfd structs. * * @author Mike McCloskey * @since 1.4 */ public abstract class AbstractPollArrayWrapper { // Miscellaneous constants static final short SIZE_POLLFD = 8; static final short FD_OFFSET = 0; static final short EVENT_OFFSET = 4; static final short REVENT_OFFSET = 6; // The poll fd array protected AllocatedNativeObject pollArray; // Number of valid entries in the pollArray protected int totalChannels = 0; // Base address of the native pollArray protected long pollArrayAddress; // Access methods for fd structures int getEventOps(int i) { int offset = SIZE_POLLFD * i + EVENT_OFFSET; return pollArray.getShort(offset); } int getReventOps(int i) { int offset = SIZE_POLLFD * i + REVENT_OFFSET; return pollArray.getShort(offset); } int getDescriptor(int i) { int offset = SIZE_POLLFD * i + FD_OFFSET; return pollArray.getInt(offset); } void putEventOps(int i, int event) { int offset = SIZE_POLLFD * i + EVENT_OFFSET; pollArray.putShort(offset, (short)event); } void putReventOps(int i, int revent) { int offset = SIZE_POLLFD * i + REVENT_OFFSET; pollArray.putShort(offset, (short)revent); } void putDescriptor(int i, int fd) { int offset = SIZE_POLLFD * i + FD_OFFSET; pollArray.putInt(offset, fd); } } int 是四个字节 见/Users/xxx/Downloads/jdk_src2/sun/nio/ch/NativeObject.java /** * Reads an address from this native object at the given offset and * constructs a native object using that address. * * @param offset * The offset of the address to be read. Note that the size of an * address is implementation-dependent. * * @return The native object created using the address read from the * given offset */ NativeObject getObject(int offset) { long newAddress = 0L; switch (addressSize()) { case 8: newAddress = unsafe.getLong(offset + address); break; case 4: newAddress = unsafe.getInt(offset + address) & 0x00000000FFFFFFFF; break; default: throw new InternalError("Address size not supported"); } return new NativeObject(newAddress); } poll file description 的结构 见 /Users/xxx/Downloads/jdk_src2/sun/nio/ch/PollArrayWrapper.java Manipulates a native array of pollfd structs on Solaris: typedef struct pollfd { int fd; short events; short revents; } pollfd_t; 一个描述符占用8个字节 jdk源码 https://yddmax.github.io/2017/06/05/openjdk%E6%BA%90%E7%A0%81%E7%9B%AE%E5%BD%95/ 后记 epoll 属于偏底层的,不太好理解。为了加深理解,可以了解下 JavaScript 的 Event Loop 或 NodeJs 的 Event Loop 参考: http://man7.org/linux/man-pages/man7/epoll.7.html https://juejin.im/entry/5b6058fde51d45348a2ffc65) https://linux.die.net/man/2/epoll_wait https://juejin.im/post/5b0524f8518825428a2631ee
池化技术 实际案例今天去公司食堂吃早点,排了一队人,先是包子馒头,然后是韭菜盒子,各种饼,最后是喝的,比如小米粥,黑米粥,豆浆等,发现队伍阻塞在最后那一段(小米粥,黑米粥,豆浆)了,这个说要黑米粥,阿姨忙不迭地去打黑米粥,那个说要小米粥,阿姨赶紧去打小米粥。。。阿姨已经没有功夫来决定打哪个了。那么我们看看另外一种情况,阿姨提前做好囤货的工作,比如小米粥,黑米粥,豆浆各打10碗放着,谁要谁自己拿。阿姨的工作就不用围着顾客团团转了,而是看哪个少了,就打哪个,比如发现黑米粥还剩下3碗了,那么就多打黑米粥。 简单来说就是提前保存大量的资源,以备不时之需 核心是复用 池化技术作用 复用相同的资源,减少浪费,减少新建和销毁的成本; 减少单独管理的成本,统一交由"池"; 集中管理,减少"碎片"; 提高系统响应速度,因为池中有现成的资源,不用重新去创建; 使用池化技术的例子 java线程池 java中所有的池化技术都有一个好处,就是通过复用池中的对象,降低系统资源消耗。设想一下如果我们有n多个子任务需要执行,如果我们为每个子任务都创建一个执行线程,而创建线程的过程是需要一定的系统消耗的,最后肯定会拖慢整个系统的处理速度。而通过线程池我们可以做到复用线程,任务有多个,但执行任务的线程可以通过线程池来复用,这样减少了创建线程的开销,系统资源利用率得到了提升! 通俗一点,线程池,就是提前创建 n 个线程在池中等待执行,当有任务过来时,直接从池中捞一个线程负责执行这个任务,执行完成之后,再放回池中,等待执行新的任务。 池中的线程可以多次利用,提高了资源利用率。因为会预制 n 个线程,所以提高了响应速度。 数据库连接池 数据库连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象。使用完毕后,用户也并非将连接关闭,而是将连接放回连接池中,以供下一个请求访问使用。而连接的建立、断开都由连接池自身来管理。同时,还可以通过设置连接池的参数来控制连接池中的初始连接数、连接的上下限数以及每个连接的最大使用次数、最大空闲时间等等,也可以通过其自身的管理机制来监视数据库连接的数量、使用情况等 java 内存池 如果每次需要内存时都是重新申请,会产生很多内存碎片,影响内存的利用率。如果一开始就申请一大块内存,按需使用,那么就会减少内存碎片。 计算系统池化技术 不同类型资源之间也存在生命周期不同步的问题。譬如,CPU 是发展速度最快的,每两三年就性能翻倍,然而存储的技术发展相对缓慢,从而会造成CPU性能和功耗的浪费。 服务器资源池化技术,除了能够带来灵活、弹性的资源部署,提高资源利用率这个优势,还能够更有效的提高服务器的故障修复能力,提升服务器运营效率。硬盘是故障率最高的部件之一,在实现存储池化技术后,多盘存储资源池为单体硬盘提供了很好的冗余设计能力。当单个硬盘出现故障后,可以及时使用其他硬盘资源进行无损恢复,无需立即现场更换硬盘 虚拟化技术 虚拟化技术和云服务都是基于虚拟化技术,将单个服务器硬件虚拟成多个虚拟机VM,其目的也是在于提高服务器资源的利用率 事件驱动开发 Event loop /Event driven development 我们想象现实生活中的例子, 以前去肯德基买吃的,需要排很长的队,排队的时候你只能干等着,啥也不能干。比如你不能去上洗手间,你不能去买汽车票,你甚至不能确定你想买的东西还有没有。 现在像肯德基这种地方都安装了自动下单的物理设备,你可以自己下单买什么,然后去干别的事情。快到你的时候,你的手机会收到提醒,然后你就直接去取餐了。 这两个场景最大的区别是什么? 前者是主动的, 后者是被动的,你会收到提醒。后者就是事件驱动的。 例子-支付成功回调 比如我们网上购物,支付订单时会跳转到第三方支付系统(比如支付宝),支付成功之后, 支付成功之后,第三方支付系统(比如支付宝)会给业务系统发送消息通知,业务系统受到通知之后,就会进行处理,比如修改订单状态,发出发货提醒等。 比如我们去食堂吃饭,如果刷工卡,刷卡成功之后,工作人员会及时从刷卡设备看到通知,就知道支付成功了,就不用找我们确认了。-- 事件驱动 如果使用手机去支付,工作人员会过一会问你下有没有支付成功,还要看你的手机界面。-- 轮询 java swing 在 java swing 中,每个界面元素(按钮,下拉框,输入框),都会绑定一个回调事件,当用户事件(例如单击,输入)发生时,系统会自动调用绑定的回调事件。 设计态(开发态)-->用户态-->自治 现在有一种趋势:很多以前需要专业人员来操作的东西,现在普通人也可以操作。比如20年前,一个内容发布系统里面的文章是需要网页编程人员来发布的,可能还需要编写 HTML,调样式, 但是现在我们不用会编程,也可以随便发表博客,比如在 简书,知乎 上发表文章。 很多公司,特别是大公司,致力于把软件开发过程中的某个环节,由开发态转化为用户态。 比如公司团队开发了通过拖拽(不用编写代码)就可以生成 HTML 页面的平台,这样非网页开发人员(比如后端)就可以自己开发网页了。 再比如Google blocky,全程用户态。 生成源代码的步骤 其中"编译",类似于我们大学课程中的编译原理。"代码自动优化":对生成的代码进行优化,比如把 if ((amout == 100)) 优化为 if (amout == 100) ,当然也有其他更复杂的优化。 模仿 Blockly 的代码生成 Google Blockly 是一款基于Web的、开源的、可视化程序编辑器。你可以通过拖拽块的形式快速构建程序,而这些所拖拽的每个块就是组成程序的基本单元。可视化编程完成,Blockly 直接支持 JavaScript、Python、PHP、Lua、Dart 语言源码的导出。此外,还可以将 Blockly 编辑器快速集成到Web、Android或iOS环境中。下面是参考Blockly 的 java 项目 界面操作(动画) 最终界面 保存生成的 json 格式 解析 生成的代码 代码生成工具 对应的界面元素 说明 代码生成的过程远比上述过程复杂,比如涉及到编译原理,抽象语法树 AST,代码优化等 参考 https://github.com/google/blockly/wiki https://developers.google.com/blockly/guides/overview https://developers.google.com/blockly/
问题重现 步骤1,把所有的mapper xml移动到 /xxx/yyy/src/main/resources/mybatis/mapperxml 中;2,修改application.properties ,添加: mybatis.mapper-locations=classpath:**/mapperxml/*.xml 3,启动springboot 程序 com/test/xxx/app/nnn/Application.java4,访问接口 http://127.0.0.1:/nnn/productManage/listYYs.json?ccKey=Key222&vvName=%AF%B7%E5%81%87App 报错: org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 初步分析 根据现象推断出,spring boot 没有读取我的 mybatis mapper xml文件 所以需要找出来,读取mybatis mapper xml文件 的逻辑在哪里,为什么没有读取? 定位问题过程 读取mapper xml文件 的逻辑/位置 /repository/org/mybatis/mybatis-spring/1.3.2/mybatis-spring-1.3.2-sources.jar!/org/mybatis/spring/SqlSessionFactoryBean.java的protected SqlSessionFactory buildSqlSessionFactory() throws IOException 方法中 读取mapper xml文件的逻辑 if (!isEmpty(this.mapperLocations)) { for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } 通过IDEA 调试到这儿的时候,发现 this.mapperLocations 为null,所以才没有走这个逻辑 为什么this.mapperLocations 为null this.mapperLocations 是在哪里设置的呢? 在 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 类中, sqlSessionFactory(DataSource dataSource) 设置this.mapperLocations的代码: if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.properties.resolveMapperLocations()); } 之所以没有执行MybatisAutoConfiguration 中的 sqlSessionFactory(DataSource dataSource) throws Exception 方法,是因为 SqlSessionFactoryBean 是我们定制的,没有依赖系统生成. 我们自定义生成SqlSessionFactoryBean 对象 final SqlSessionFactoBean sessionFactoryBean = new SqlSessionFactoBean(); sessionFactoryBean.setDataSource(aaaGroupDataSource); SqlSessionFactory sqlSessionFactory = sessionFactoryBean.getObject(); // mybatis自动通用字段自动设置配置 org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); return sqlSessionFactory; 系统构造SqlSessionFactory 在/repository/org/mybatis/mybatis-spring/1.3.6/mybatis-spring-1.3.6-sources.jar!/org/mybatis/spring/SqlSessionFactoryBean.java 的 protected SqlSessionFactory buildSqlSessionFactory() throws IOException 原因总结 因为我们自定义生成 SqlSessionFactoryBean对象 导致没有执行org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration 类的sqlSessionFactory(DataSource dataSource)方法 解决方法 手动添加设置this.mapperLocations 的逻辑即可: final SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); //解决 application.properties 中 配置mybatis.mapper-locations 失效的问题 if (!ObjectUtils.isEmpty(this.mybatisProperties.resolveMapperLocations())) { sessionFactoryBean.setMapperLocations(this.mybatisProperties.resolveMapperLocations()); } sessionFactoryBean.setDataSource(aaaGroupDataSource); SqlSessionFactory sqlSessionFactory = sessionFactoryBean.getObject(); // mybatis自动通用字段自动设置配置 org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); // 。。。 return sqlSessionFactory; //解决 application.properties 中 配置mybatis.mapper-locations 失效的问题 <span data-type="color" style="color:#F5222D">if (!ObjectUtils.isEmpty(this.mybatisProperties.resolveMapperLocations())) {</span> sessionFactoryBean.setMapperLocations(this.mybatisProperties.resolveMapperLocations()); } tips 若想亲自调试,可以关注如下类: org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration org.mybatis.spring.SqlSessionFactoryBean org.apache.ibatis.session.SqlSessionFactoryBuilder
目前OAuth 2.0 认证有五种方式,见官方文档:http://www.rfcreader.com/#rfc6749 授权码模式(Authorization Code Grant) 流程图 说明:1,认证服务器是一个业务无关的系统;2,code就是授权码,只能使用一次;3,token 在有效期内可以无限次使用 场景化讲解-下单 场景:A 公司旗下有很多 web 产品,同时还有一个应用商店(类似京东)负责售卖这些软件产品,下面以用户进入应用商店购买产品的流程着手。 应用商店通过浏览器尝试获取下单页; 应用商店后台鉴权,发现没有登录(应用商店后台实际会调用认证服务器,比如校验 access tokensh是否有效); 跳转到登录页面;(对应上图(A)) 用户通过浏览器输入用户名,密码等方式进行授权(此时会调用认证服务器接口);(对应上图(B)) 认证服务器返回临时 code 给浏览器;(对应上图(C)) 应用商店拿临时 code 去调用认证服务器接口,换取 access token;(对应上图(D)) 认证服务器返回 access token给应用商店;(对应上图(E)) 应用商店把 access token 缓存下来。 后续应用商店调用业务系统(比如下单接口,订单查询接口)接口均带上 access token; 场景化讲解-微信公众号授权 现在很多微商系统接入了微信公众号,通过微信公众号售卖产品,那么它们是如何获取用户信息的呢?我们在关注某个商店性质的微信公众号时,经常会弹出如下对话框,要求我们授权图2-1用户点击上述界面中的"允许",对应的操作是“"授权码模式"的(B)-- User authenticates: 具体流程如下: 某微信公众号B 访问用户信息,用于下单支付(实际需要用户的 openid); 见图2-1,微信弹框,要求用户授权; 用户点击"允许"授权,某微信公众号B才能获取到临时 code; 某微信公众号B拿临时 code调用微信鉴权接口获取 access token 或 openid; 实例3-对接钉钉通讯录实现登录 免登流程 第一步:通过用户授权获取code授权链接:https://h5.dingtalk.com/liveeasylogin/index.html#/index?appid=dingoae2vmockd44q2&response_type=code&scope=snsapi_auth&state=STATE&redirect_uri=http%3A%2F%2Flocalhost%3A7001%2Fapi%2Ftest%2FtestCode.json 第二步:用户授权 授权成功获取code {"content":"code=4d9954574c32afafd9xxx3c34b414aa&state=STATE","errorLevel":0,"success":true} code 值为:4d9954574c32afafd9xxx3c34b414aa 授权测试地址 dingtalk://dingtalkclient/page/link?url=https%3A%2F%2Fh5.dingtalk.com%2Fliveeasylogin%2Findex.html%23%2Findex%3Fappid%3Ddingoaemockdlfr66612%26response_type%3Dcode%26scope%3Dsnsapi_auth%26state%3DSTATE%26redirect_uri%3Dhttp%253A%252F%252Floca77host%253A7001%252Fapi%252Ftest%252FtestCode.json%3fddtab%3dtrue code有什么用? 可以获取用户信息; 可以获取access_token 获取用户信息 //获取用户信息 DefaultDingTalkClient client = new DefaultDingTalkClient(DingTalkConstants.URL_GET_USER_INFO_BY_CODE); OapiSnsGetuserinfoBycodeRequest request = new OapiSnsGetuserinfoBycodeRequest(); request.setTmpAuthCode(requestAuthCode); request.setHttpMethod(HttpMethod.POST.toString()); OapiSnsGetuserinfoBycodeResponse response; try { response = client.execute(request, dingTalkProperties.getAppid(), dingTalkProperties.getAppsecret()); } catch (ApiException e) { logger.error("accessToken:{0}, url:{1}", "", DingTalkConstants.URL_GET_USER_INFO_BY_CODE, e); return null; } LoginedUser loginedUser = null; if (response.isSuccess() && null != response.getUserInfo()) { OapiSnsGetuserinfoBycodeResponse.UserInfo userInfo = response.getUserInfo(); 获取access_token 略 参考 http://www.rfcreader.com/#rfc6749
理解 责任链的执行序列是一环连着一环,后一环的执行依赖前一环的执行,层层递进. 流程图 组成部分 链条:负责承前启后; 链条上的环:执行业务逻辑,决定是否继续执行下一个环(有的情况由链条决定); 执行流: 前期:由外层到里层; 后期:由里层到外层 特点 链条上相邻的环之间会影响,影响入参和出参; 每个环负责相对独立的任务,比如有的负责字符编码设置,有的负责流的编码和解码(比如gzip),有的负责记录日志等 实例 一个简单的 demo 项目代码 链条类 public class DaoFilterChain { private List<IFilter> filterList = new ArrayList<>(); private int index = 0; private boolean hasAddDefaultFilter = false; public DaoFilterChain addFilter(IFilter filter) { if (hasAddDefaultFilter) { throw new RuntimeException("自定义过滤器必须在默认过滤器之前添加"); } this.filterList.add(filter); return this; } /*** * 专门添加最基础的DaoFilter * @param filter * @return */ public DaoFilterChain addDefaultFilter(IFilter filter) { this.filterList.add(filter); hasAddDefaultFilter = true; return this; } public void reset() { this.index = 0; } private IFilter next() { if (index == filterList.size()) { return null; } IFilter filter = filterList.get(index); index++; return filter; } public void doFilter(DaoRequest request, DaoResponse response) { IFilter filter = next(); if (null == filter) { System.out.println("结束 index :" + index); return; } filter.doFilter(request, response, this); } } 过滤器接口 public interface IFilter { /*** * 最后一个过滤器一定是DefaultDaoFilter * @param request * @param response * @param filterChain */ void doFilter(DaoRequest request, DaoResponse response, DaoFilterChain filterChain); } javax.servlet.Filter 过滤器核心方法 : public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; 链条:org.apache.catalina.core.ApplicationFilterChain源码见:http://www.docjar.com/html/api/org/apache/catalina/core/ApplicationFilterChain.java.html doFilter 核心代码 (去掉了其他代码): ApplicationFilterConfig filterConfig = filters[pos++]; Filter filter = null; filter = filterConfig.getFilter(); filter.doFilter(request, response, this); 过滤器实例 :支持跨域其他过滤器实例 租户拦截器 接口: public interface IRequestFilter { void doFilter(HttpFormContentRequestWrapper request, HttpServletResponse response, RequestFilterChain filterChain) throws IOException, ServletException; } 链条类 RequestFilterChain public class RequestFilterChain { private List<IRequestFilter> filterList = new ArrayList<>(); private final static Logger logger = LoggerFactory.getLogger(RequestFilterChain.class); private ThreadLocal<Integer> indexLocal = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 0; } }; private ThreadLocal<Boolean> hasAddDefaultFilter = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return Boolean.FALSE; } }; public RequestFilterChain addFilter(IRequestFilter filter) { if (hasAddDefaultFilter.get()) { throw new RuntimeException("自定义过滤器必须在默认过滤器之前添加"); } this.filterList.add(filter); return this; } public RequestFilterChain removeFilter(IRequestFilter filter) { this.filterList.remove(filter); return this; } public RequestFilterChain addDefaultFilter(IRequestFilter filter) { this.filterList.add(filter); hasAddDefaultFilter.set(true); // DefaultFormRequestWrapperFilter defaultDaoFilter = (DefaultFormRequestWrapperFilter) filter; return this; } public void reset() { indexLocal.set(0); } private IRequestFilter next() { if (indexLocal.get() == filterList.size()) { return null; } IRequestFilter filter = filterList.get(indexLocal.get()); // index++; indexLocal.set(indexLocal.get() + 1); return filter; } public void doFilter(HttpFormContentRequestWrapper request, HttpServletResponse response) throws IOException, ServletException { IRequestFilter filter = next(); if (null == filter) { return; } try { filter.doFilter(request, response, this); } catch (Exception e) { e.printStackTrace(); throw e; } } } MultipleTenantsFilter 做了什么 适用场景 同时有请求和应答的场景,例如HTTP请求,数据库查询; 映射层转化 需要决定是否中断流程的执行; 业务的执行分为多个中间环节,中间环节的个数可以定制; 和aop的区别 aop体现的是代理模式,实际是动态代理或 cglib 感想 责任链模式,依然符合开闭原则,比如由于业务的发展,需要添加新的逻辑,那么就可以增加一个过滤器,而不用修改原来的逻辑处理; 对于逻辑比较复杂,且容易变动的逻辑,可以充分使用责任链模式,把相对聚合的逻辑抽取出来,作为一个个环,后面增加新的逻辑只需要增加新的环即可,也可以根据实际需求修改链条上环的顺序。
向量 向量是由n个实数组成的一个n行1列(n1)或一个1行n列(1n)的有序数组;a=[1,2,3,4]b=[6,7,8,9](x,y)表示二维空间(即面)上面的一个点;(x,y,z)表示三维空间中的一个点x 1,x 2,x 3.。。x n 表示n维空间一个具体的点 点乘 点乘也叫 点积对于向量a和向量b:a和b的点积公式为 余弦定理 sin cos 1、sin 30= 1/2c 是斜边c=a*2 --> a/c=0.5=sin 302、sin 45=根号2/2a==b --> a/c=根号2/23、sin 60= 根号3/2 切线的斜率 斜率就是该点的切线,坐标y/x的值比如下面的曲线(y=x)的斜率就是1 导数 导数 f ′ (x) 代表 f(x) 在点 x 处的斜率。换句话说,它表明如何缩放输入的小变化才能在输出获得相应的变化:f(x + ϵ) ≈ f(x) + ϵf ′ (x)。说白了,就是衡量 x 变化时,y 变化的方向和快慢(不是速度,而是加速度)例如 函数 y=f(x)=3x+1x 增加2,y 增加6,x 增加3,y 增加9,x 增加4,y 增加12,....。。。那么 y 增加的速度就是恒定的,是3(9-6)实际上,y=f(x)=3x+1的 导数 f ′ (x) 就是3此时,随着变量 x的变化,y的变化方向和快慢是固定的,是常量f(x + ϵ) ≈ f(x) + ϵf ′ (x)f(x + ϵ) =x3+1+ϵ3,基本初等函数的导数公式 log 和 ln ln x,表示e的多少次方等于xe是常量,值为:2.718表示 e的2.9957次方等于20logx y 表示x的多少次方等于y 根号 根号x 表示 什么数的平方等于x 拟合 目标/损失函数 针对任何模型求解问题,都是最终都是可以得到一组预测值y^ ,对比已有的真实值 y ,数据行数为 n ,可以将损失函数定义如下:即预测值与真实值之间的平均的平方距离,统计中一般称其为MAE(mean square error)均方误差。把之前的函数式代入损失函数,并且将需要求解的参数w和b看做是函数L的自变量,可得注意 表示预测值 梯度下降 参考 https://www.yuque.com/whuanghkl/bbxlpq/dfkpin#YwmMs导数有什么用呢?通过导数,可以预测f(x)变化的趋势,如果 导数<0,表示f(x) 随着x的变化在变小,如果 导数>0,表示f(x) 随着x的变化在变大,因此导数对于最小化一个函数很有用,因为它告诉我们如何更改 x 来略微地改 善 y。例如,我们知道对于足够小的 ϵ 来说,f(x − ϵsign(f ′ (x))) 是比 f(x) 小的。因 此我们可以将 x 往导数的反方向移动一小步来减小 f(x)。这种技术被称为 梯度下降 (gradient descent)(Cauchy, 1847)。图 4.1 展示了一个例子。 数学工具,数学软件 符号说明 https://www.yuque.com/whuanghkl/bbxlpq/untwaa#9GZct
(1)Java代码通过keystore文件获取私钥报错 使用keytool 工具生成keystore文件,然后通过java 获取私钥privateKey 时,报错: Xml代码 java.security.UnrecoverableKeyException: Cannot recover key at sun.security.provider.KeyProtector.recover(KeyProtector.java:311) at sun.security.provider.JavaKeyStore.engineGetKey(JavaKeyStore.java:121) at sun.security.provider.JavaKeyStore$JKS.engineGetKey(JavaKeyStore.java:38) at java.security.KeyStore.getKey(KeyStore.java:763) at com.jn.test.TestCA.test_01(TestCA.java:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) 具体操作如下 使用keystool 生成本地数字证书 Java代码 keytool -genkeypair -keyalg RSA -keysize 2048 -sigalg SHA1withRSA -validity 36000 -alias localhost -storepass abcdefg -keystore zlex.keystore -dname "CN=localhost, OU=zlex,O=zlex, L=BJ, ST=BJ, C=CN" 运行结果: 说明:keystore的密码是abcdefg,通过-storepass 指定。 java 代码如下: Java代码 @Test public void test_01() throws Exception { String keyStorePath="d:\\Temp\\a\\a\\ca\\zlex.keystore"; String password="abcdefg"; // 获得密钥库 KeyStore ks = getKeyStore(keyStorePath, password); // 获得私钥 PrivateKey privateKey = (PrivateKey) ks.getKey("localhost", password.toCharArray()); System.out.println(privateKey); } /** * 获得KeyStore * * @param keyStorePath * 密钥库路径 * @param password * 密码 * @return KeyStore 密钥库 */ private static KeyStore getKeyStore(String keyStorePath, String password) throws Exception { // 实例化密钥库 KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // 获得密钥库文件流 FileInputStream is = new FileInputStream(keyStorePath); // 加载密钥库 ks.load(is, password.toCharArray()); // 关闭密钥库文件流 is.close(); return ks; } 运行上述java 代码时,报错:java.security.UnrecoverableKeyException: Cannot recover key 到底是什么原因呢? 原因:keystore 密码和主密码不同。 解决方法:keystore 密码和主密码使用相同的密码。 详情请参考:http://stackoverflow.com/questions/4926290/java-keystore-and-password-settings (2)tomcat使用keystore文件启动报错 若keystore 密码和主密码不同,启动tomcat时也会报错 命令:keytool -genkey -alias tomcat -keyalg RSA -keysize 1024 -validity 365 -keystore tomcat22.keystore 生成的文件 就是:tomcat22.keystore 密码一和 密码二必须相同,否则,启动tomcat 时会报错。
项目是Java Web; 使用框架:struts,hibernate,spring; 构建工具:maven, 使用一个商品管理系统,用于管理超市和商品。 项目结构如下: 执行如下操作时报错: 点击“delete”,删除超市时,报错。 注意:是有时候报错,不是每次删除都报错。 报错信息如下: Xml代码 Struts Problem Report Struts has detected an unhandled exception: Messages: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 File: org/hibernate/jdbc/Expectations.java Line number: 81 Stacktraces org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 org.springframework.orm.hibernate4.SessionFactoryUtils.convertHibernateAccessException(SessionFactoryUtils.java:181) org.springframework.orm.hibernate4.HibernateTransactionManager.convertHibernateAccessException(HibernateTransactionManager.java:680) org.springframework.orm.hibernate4.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:562) org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:755) org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:724) org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:475) org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:270) org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:91) org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:631) com.shop.jn.dao.SupermarketDao$$EnhancerByCGLIB$$2c3435ff.delete() com.shop.jn.action.supermarket.DeleteSupermarketAction.execute(DeleteSupermarketAction.java:20) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) java.lang.reflect.Method.invoke(Method.java:597) com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:453) com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:292) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:255) com.shop.jn.interceptor.LoggerInterceptor.intercept(LoggerInterceptor.java:23) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.debugging.DebuggingInterceptor.intercept(DebuggingInterceptor.java:256) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor.doIntercept(DefaultWorkflowInterceptor.java:176) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.validator.ValidationInterceptor.doIntercept(ValidationInterceptor.java:265) org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:68) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:236) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:236) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.StaticParametersInterceptor.intercept(StaticParametersInterceptor.java:190) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.MultiselectInterceptor.intercept(MultiselectInterceptor.java:75) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.CheckboxInterceptor.intercept(CheckboxInterceptor.java:90) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.FileUploadInterceptor.intercept(FileUploadInterceptor.java:243) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor.intercept(ModelDrivenInterceptor.java:100) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor.intercept(ScopedModelDrivenInterceptor.java:141) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ChainingInterceptor.intercept(ChainingInterceptor.java:145) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.PrepareInterceptor.doIntercept(PrepareInterceptor.java:171) com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.I18nInterceptor.intercept(I18nInterceptor.java:176) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.interceptor.ServletConfigInterceptor.intercept(ServletConfigInterceptor.java:164) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.AliasInterceptor.intercept(AliasInterceptor.java:192) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor.intercept(ExceptionMappingInterceptor.java:187) com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249) org.apache.struts2.impl.StrutsActionProxy.execute(StrutsActionProxy.java:54) org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:511) org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:77) org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:91) org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235) org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191) org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127) org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298) org.apache.coyote.http11.Http11AprProcessor.process(Http11AprProcessor.java:859) org.apache.coyote.http11.Http11AprProtocol$Http11ConnectionHandler.process(Http11AprProtocol.java:579) org.apache.tomcat.util.net.AprEndpoint$Worker.run(AprEndpoint.java:1555) java.lang.Thread.run(Thread.java:662) org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 原因分析: 执行删除超市的action 是DeleteSupermarketAction,内容如下: Java代码 package com.shop.jn.action.supermarket; import com.opensymphony.xwork2.ActionSupport; import com.shop.jn.dao.SupermarketDao; import com.shop.jn.entity.Supermarket; /*** * 删除超市. * * @author huangwei * */ public class DeleteSupermarketAction extends ActionSupport { private static final long serialVersionUID = 7753616847784594121L; private Supermarket supermarket; private SupermarketDao supermarketDao; @Override public String execute() throws Exception { this.supermarketDao.delete(this.supermarket); return super.execute(); } public Supermarket getSupermarket() { return supermarket; } public void setSupermarket(Supermarket supermarket) { this.supermarket = supermarket; } public SupermarketDao getSupermarketDao() { return supermarketDao; } public void setSupermarketDao(SupermarketDao supermarketDao) { this.supermarketDao = supermarketDao; } } 说明:supermarket 是通过依赖注入的,注入的字段是id(http://localhost:8080/shop_goods/supermarket/deleteSupermarket.action?supermarket.id=9)。 此时supermarket 的状态是detached 脱管的(脱离管理的),this.supermarketDao.delete 的方法体如下: Java代码 this.sessionFactory.getCurrentSession().delete(obj); 但是hibernate api中对delete 的方法说明如下: void delete(Object object) Remove a persistent instance from the datastore. 要求参数必须是persistent 持久化的的状态。 如何解决呢? 使用原生的sql语句 我在dao中新增了一个方法: Java代码 public void deleteHQL(int id) { this.sessionFactory.getCurrentSession() .createQuery("delete from Supermarket where id=?") .setInteger(0, id).executeUpdate(); } 在DeleteSupermarketAction 中调用如下: Java代码 @Override public String execute() throws Exception { this.supermarketDao.deleteHQL(this.supermarket.getId()); return super.execute(); }
Java 中如何使用base64编码呢? 有如下三种方式: 方式一:commons-codec.jar Java代码 String base64String = "whuang123"; byte[] result = Base64.encodeBase64(base64String.getBytes()); 方式二:使用sun.misc.BASE64Encoder Java代码 /** * 编码 * * @param bstr * @return String */ public static String encode(byte[] bstr) { return new sun.misc.BASE64Encoder().encode(bstr); } /** * 解码 * * @param str * @return string */ public static byte[] decode(String str) { byte[] bt = null; try { sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); bt = decoder.decodeBuffer(str); } catch (IOException e) { e.printStackTrace(); } return bt; } 方式三:使用com.sun.org.apache.xerces.internal.impl.dv.util.Base64 Java代码 /*** * encode by Base64 */ public static String encodeBase64(byte[] input) throws Exception { Class clazz = Class .forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64"); Method mainMethod = clazz.getMethod("encode", byte[].class); mainMethod.setAccessible(true); Object retObj = mainMethod.invoke(null, new Object[] { input }); return (String) retObj; } /*** * decode by Base64 */ public static byte[] decodeBase64(String input) throws Exception { Class clazz = Class .forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64"); Method mainMethod = clazz.getMethod("decode", String.class); mainMethod.setAccessible(true); Object retObj = mainMethod.invoke(null, input); return (byte[]) retObj; } 测试: Java代码 package com.jn.base64; import junit.framework.Assert; import org.apache.commons.codec.binary.Base64; import com.common.util.SystemUtil; public class BaseTest { public static void main(String[] args) throws Exception { String base64String = "whuang123"; byte[] result = Base64.encodeBase64(base64String.getBytes()); SystemUtil.printBytes(result); byte[] result2 = SystemUtil.encode(base64String.getBytes()).getBytes(); System.out.println("result2:"+result2); byte[] result3 = SystemUtil.encodeBase64(base64String.getBytes()).getBytes(); boolean isSuccess = SystemUtil.isSame(result, result2); Assert.assertEquals(true, isSuccess); SystemUtil.printBytes(result2); SystemUtil.printBytes(result3); System.out.println(isSuccess); } } 运行结果如下:
使用jdk自带的zip工具类Java.util.zip.ZipEntry,java.util.zip.ZipFile,java.util.zip.ZipInputStream,java.util.zip.ZipOutputStream 进行zip压缩时,没法解决文件名中文乱码问题 这里使用apache 旗下的commons-compress 库,官网是:http://commons.apache.org/proper/commons-compress/download_compress.cgi 我使用maven 进行构建,pom配置如下: Xml代码 <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-compress</artifactId> <version>1.5</version> </dependency> commons-compress 解决了文件名中文乱码问题,参考:http://www.cnblogs.com/un4sure/archive/2011/09/27/2193298.html 范例: (1)压缩单个文件: 压缩D:\\Temp\\a\\password_密码.xls,压缩后的zip文件是d:\\Temp\\a\\a\\b\\c.zip Java代码 package com.jn.test; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import org.apache.commons.compress.archivers.ArchiveException; import org.apache.commons.compress.archivers.ArchiveOutputStream; import org.apache.commons.compress.archivers.ArchiveStreamFactory; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.junit.Test; import com.common.util.SystemUtil; import com.io.hw.file.util.FileUtils; public class ZIPTest { @Test public void test_01() { try { FileOutputStream fou = new FileOutputStream("d:\\Temp\\a\\a\\b\\c.zip"); ArchiveOutputStream archOuts = new ArchiveStreamFactory() .createArchiveOutputStream(ArchiveStreamFactory.ZIP, fou); if(archOuts instanceof ZipArchiveOutputStream){ ZipArchiveOutputStream zipOut=(ZipArchiveOutputStream)archOuts; String file="D:\\Temp\\a\\password_密码.xls"; ZipArchiveEntry zipEntry=new ZipArchiveEntry(new File(file),SystemUtil.getFileSimpleName(file)); zipOut.putArchiveEntry(zipEntry); zipOut.write(FileUtils.readBytes4file(file)); zipOut.closeArchiveEntry(); zipOut.flush(); zipOut.finish(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (ArchiveException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } 运行之后会生成文件d:\\Temp\\a\\a\\b\\c.zip: (1)压缩多个文件: Java代码 @Test public void test_02() { try { FileOutputStream fou = new FileOutputStream( "d:\\Temp\\a\\a\\b\\c.zip"); ArchiveOutputStream archOuts = new ArchiveStreamFactory() .createArchiveOutputStream(ArchiveStreamFactory.ZIP, fou); if (archOuts instanceof ZipArchiveOutputStream) { ZipArchiveOutputStream zipOut = (ZipArchiveOutputStream) archOuts; String file01 = "D:\\Temp\\a\\password_密码.xls"; ZipArchiveEntry zipEntry = new ZipArchiveEntry( new File(file01), SystemUtil.getFileSimpleName(file01)); zipOut.putArchiveEntry(zipEntry); zipOut.write(FileUtils.readBytes4file(file01)); String file02 = "D:\\Temp\\a\\ccc.jar"; ZipArchiveEntry zipEntry2 = new ZipArchiveEntry( new File(file01), SystemUtil.getFileSimpleName(file02)); zipOut.putArchiveEntry(zipEntry2); zipOut.write(FileUtils.readBytes4file(file02)); zipOut.closeArchiveEntry(); zipOut.flush(); zipOut.finish(); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (ArchiveException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 压缩后的zip文件:
Java 如何无损读取文本文件呢? 以下是有损的 Java代码 @Deprecated public static String getFullContent(File file, String charset) { BufferedReader reader = null; if (!file.exists()) { System.out.println("getFullContent: file(" + file.getAbsolutePath() + ") does not exist."); return null; } if (charset == null) { charset = SystemHWUtil.CHARSET_ISO88591; } try { reader = getBufferReaderFromFile(file, charset); return getFullContent(reader); } catch (FileNotFoundException e1) { e1.printStackTrace(); } finally { if (null != reader) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } public static BufferedReader getBufferReaderFromFile(File file, String charset) throws FileNotFoundException { InputStream ss = new FileInputStream(file); InputStreamReader ireader; BufferedReader reader = null; try { if (charset == null) { ireader = new InputStreamReader(ss, SystemHWUtil.CHARSET_ISO88591); } else { ireader = new InputStreamReader(ss, charset); } reader = new BufferedReader(ireader); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return reader; } /** * have closed reader * * @param reader * @return */ @Deprecated public static String getFullContent(BufferedReader reader) { StringBuilder sb = new StringBuilder(); String readedLine = null; try { while ((readedLine = reader.readLine()) != null) { sb.append(readedLine); sb.append(SystemHWUtil.CRLF); } } catch (IOException e) { e.printStackTrace(); } finally { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } String content = sb.toString(); int length_CRLF = SystemHWUtil.CRLF.length(); if (content.length() <= length_CRLF) { return content; } return content.substring(0, content.length() - length_CRLF);// } 测试: Java代码 @Test public void test_getFullContent(){ String filepath="D:\\bin\\config\\conf_passwd.properties"; try { InputStream in =new FileInputStream(filepath); System.out.print(FileUtils.getFullContent(filepath, "UTF-8")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 介绍三种无损读取的方式 方式一:使用InputStreamReader,指定编码 Java代码 /*** * 指定字符编码,无损地读取文本文件. * * @param in * : 输入流,会关闭 * @param charset * : 字符编码 * @return * @throws IOException */ public static String getFullContent3(InputStream in, String charset) throws IOException { StringBuffer sbuffer = new StringBuffer(); InputStreamReader inReader; //设置字符编码 inReader = new InputStreamReader(in, charset); char[] ch = new char[SystemHWUtil.BUFF_SIZE_1024]; int readCount = 0; while ((readCount = inReader.read(ch)) != -1) { sbuffer.append(ch, 0, readCount); } inReader.close(); in.close(); return sbuffer.toString(); } 测试: Java代码 @Test public void test_getFullContent3(){ String filepath="D:\\bin\\config\\conf_passwd.properties"; try { InputStream in =new FileInputStream(filepath); System.out.print(FileUtils.getFullContent3(in, "UTF-8")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } 方式二:先读取出字节数组,再使用String的构造方法 Java代码 public static String getFullContent4(InputStream in, String charset) throws IOException{ byte[]bytes=FileUtils.readBytes3(in); return new String(bytes,charset); } /*** * Has been tested * * @param in * @return * @throws IOException */ public static byte[] readBytes3(InputStream in) throws IOException { BufferedInputStream bufin = new BufferedInputStream(in); int buffSize = BUFFSIZE_1024; ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize); // System.out.println("Available bytes:" + in.available()); byte[] temp = new byte[buffSize]; int size = 0; while ((size = bufin.read(temp)) != -1) { out.write(temp, 0, size); } bufin.close(); in.close(); byte[] content = out.toByteArray(); out.flush(); out.close(); return content; } 方式三:使用System.arraycopy,所以效率不高,因为有拷贝操作(不推荐) Java代码 public static String getFullContent2(InputStream in, String charset) throws IOException { int step = BUFFSIZE_1024; BufferedInputStream bis = new BufferedInputStream(in); // Data's byte array byte[] receData = new byte[step]; // data length read from the stream int readLength = 0; // data Array offset int offset = 0; // Data array length int byteLength = step; while ((readLength = bis.read(receData, offset, byteLength - offset)) != -1) { // Calculate the current length of the data offset += readLength; // Determine whether you need to copy data , when the remaining // space is less than step / 2, copy the data if (byteLength - offset <= step / 2) { byte[] tempData = new byte[receData.length + step]; System.arraycopy(receData, 0, tempData, 0, offset); receData = tempData; byteLength = receData.length; } } return new String(receData, 0, offset, charset); }
在使用json-lib 反序列化时,报错: Exception in thread "main" Java.lang.ClassCastException: net.sf.ezmorph.bean.MorphDynaBean cannot be cast to com.jn.json.bean.Student at com.jn.test.Test2.test_reserialize(Test2.java:104) at com.jn.test.Test2.main(Test2.java:110) 项目结构如下(源代码见附件): 依赖的库如下: json-lib-2.1-jdk15.jar 测试代码如下:把字符串转化为java对象 Java代码 public static void test_reserialize(){ // String jsonInput="{\"className\":\"计算机0705\",\"count\":0,\"students\":[{\"addrr\":null,\"age\":0,\"hobby\":\"\",\"name\":\"\"}]}"; String jsonInput="{\"classAttribute\":{\"pp1\":\"vv1\",\"pp2\":\"vv2\"},\"className\":\"计算机0705\",\"count\":0,\"students\":[{\"addrr\":null,\"age\":0,\"attribute\":{\"p2\":\"v2\",\"p1\":\"v1\"},\"hobby\":\"\",\"name\":\"\"}]}"; JSONObject js = JSONObject.fromObject(jsonInput); Class2 one = (Class2) JSONObject.toBean(js, Class2.class); System.out.println(one.getClassName()); Map<String, Object> attribute =one.getStudents().get(0).getAttribute(); System.out.println(attribute); } public static void main(String[] args) { test_reserialize(); } 运行时报错: 解决方法: 使用JsonConfig,修改后的代码如下: Java代码 public static void test_reserialize(){ String jsonInput="{\"classAttribute\":{\"pp1\":\"vv1\",\"pp2\":\"vv2\"},\"className\":\"计算机0705\",\"count\":0,\"students\":[{\"addrr\":null,\"age\":0,\"attribute\":{\"p2\":\"v2\",\"p1\":\"v1\"},\"hobby\":\"\",\"name\":\"\"}]}"; JSONObject js = JSONObject.fromObject(jsonInput); JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setRootClass(Class2.class); Map<String, Class> classMap = new HashMap<String, Class>(); classMap.put("students", Student.class); // 指定JsonRpcRequest的request字段的内部类型 jsonConfig.setClassMap(classMap); Class2 one = (Class2) JSONObject.toBean(js, jsonConfig); System.out.println(one.getClassName()); Map<String, Object> attribute =one.getStudents().get(0).getAttribute(); System.out.println(attribute); } 主要增加了: JsonConfig jsonConfig = new JsonConfig(); jsonConfig.setRootClass(Class2.class); Map<String, Class> classMap = new HashMap<String, Class>(); classMap.put("students", Student.class); // 指定JsonRpcRequest的request字段的内部类型 jsonConfig.setClassMap(classMap);
在页面中如何获取cookie值呢? 如果是JSP的话,可以通过servlet的对象request 获取cookie,可以 参考:http://hw1287789687.iteye.com/blog/2050040 如果要求登录页面是html呢?html页面中如何获取cookie呢? 直接上代码了 页面:loginInput.html 代码: Html代码 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html><head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <!-- base href="http://localhost:8080/shop_goods/" --> <title>user login</title> <meta http-equiv="pragma" content="no-cache"> <meta http-equiv="cache-control" content="no-cache"> <meta http-equiv="expires" content="0"> <meta http-equiv="keywords" content="keyword1,keyword2,keyword3"> <meta http-equiv="description" content="This is my page"> <script language="JavaScript" src="/shop_goods/js/common_util.js" type="text/javascript"></script> <!-- <link rel="stylesheet" type="text/css" href="styles.css"> --> <style type="text/css"> .errorMessage li { list-style-type: none; margin-left: 0 } </style> </head> <body> <script type="text/javascript"> //获取cookie的值 function getCookie(cookieKey){ var cookies = document.cookie ? document.cookie.split('; ') : []; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split('='); if(parts.length>1){ if(parts[0]==cookieKey){ //username1=; return parts[1]; } } } return ''; } var username1=''; window.onload=function(){ //cookie的key是'userEmail' username1=getCookie('userEmail'); //alert("username1:"+username1); var issave222=com.whuang.hsj.$$one("issave"); if(username1){ if(username1!='' && username1!=null &&username1!=undefined){ com.whuang.hsj.$$one("user.username").value=username1; issave222.checked=true; }else{ issave222.checked=false; } }else{ issave222.checked=false; } } </script> This is login page. <br> <a href="/shop_goods/">index</a> <br> <a href="/shop_goods/user/registerUser.jsp">register user</a> <font color="red"></font> <font style="font-weight: bold" color="red"> </font> <form action="/shop_goods/user/login" method="post"> <table> <tbody><tr> <td>username:</td> <td><input name="user.username" id="user_username" type="text"> </td> </tr> <tr> <td>password:</td> <td><input name="user.password" id="user_password" type="text"> </td> </tr> <tr> <td colspan="2"> <input name="issave" value="save" type="checkbox"> 保存用户名</td></tr> <tr> <td colspan="2"><input id="" value="login" type="submit"> </td> </tr> </tbody></table> </form></body></html>
接着上一篇博客:http://hw1287789687.iteye.com/blog/2053907 (1)我们上周做一个web项目,修改bug后发增量包(补丁包),其实我喜欢发全量包,但是领导要求增量包(补丁包),没办法. 有次发增量包(补丁包),修改了一个常量类,发布后,仍然测出问题.纳闷:明明修改了常量类啊.反编译常量类,确实是修改过之后的,没问题.那么问题出在哪儿呢? 给大伙儿重现一下: 我这里有两个类 Java代码 public class Constant { public static final String WHO="黄威"; public static final int AGE=26; } public class Hello{ public static void main(String[]args) { System.out.println("Hello,"+Constant.WHO); } } 在命令行中进行编译: 运行: 然后我发一个增量包:只修改Constant类,修改Constant类的WHO变量: Java代码 public class Constant { public static final String WHO="黄威22222222222222"; public static final int AGE=26; } 单独编译Constant:,然后运行Hello: 奇怪!!!为什么结果没有变呢? 预期的结果应该是: Hello,黄威22222222222222 原因是:对于含有常量的类,javac编译时直接把常量的值替换进去了. 所以我们还得重新编译Hello: 测试代码见附件 (2)对于js动态增加的表格tr,IE浏览器不识别 我使用如下代码动态增加表格的行(tr): Js代码 var queryResultTable_obj=getTable(); dataLength=tableContent.length; for(var i=0;i<dataLength;i++){ var oneTr=tableContent[i]; //alert(oneTr.realName); var newTr = document.createElement("tr"); var newTd0 = document.createElement("td"); var newTd1 = document.createElement("td"); var newTd2 = document.createElement("td"); var newTd3 = document.createElement("td"); var newTd4 = document.createElement("td"); newTd0.innerHTML =oneTr.realName; newTd1.innerHTML =oneTr.email; newTd2.innerHTML =oneTr.aaa; newTd3.innerHTML =oneTr.bbb; newTd4.innerHTML ="<a target='_blank' href=\""+"../mgmt/personDetail.action?realName="+oneTr.realName+"&email="+oneTr.email+"&query_time="+query_time+"\" >签到详情</a>" newTr.appendChild(newTd0); newTr.appendChild(newTd1); newTr.appendChild(newTd2); newTr.appendChild(newTd3); newTr.appendChild(newTd4); queryResultTable_obj.appendChild(newTr); } 在IE中使用queryResultTable_obj.rows.length 获取表格行的个数时竟然是0,但是在火狐和谷歌浏览器中都没有问题. 说明:queryResultTable_obj 是表格对象 那么在IE中如何获取表格的行(tr)呢? 通过 var trs=queryResultTable_obj.getElementsByTagName("tr");//获取表格所有的行tr 如何动态删除表格的所有行(除了表头)呢? Js代码 //判断是否是IE浏览器 var userAgent = navigator.userAgent.toLowerCase(); var browser=navigator.appName; var b_version=navigator.appVersion; //var version=b_version.split(";"); //var trim_Version=version[1].replace(/[ ]/g,"");//firefox error var isIE9test=userAgent.indexOf("windows nt ")>0&&userAgent.indexOf("trident")>0&&browser=="Microsoft Internet Explorer"; //删除行 function deleteRow(){ var queryResultTable_obj=getTable(); if(isIE9test){//如果是IE浏览器 var trs=queryResultTable_obj.getElementsByTagName("tr");//获取表格所有的行tr for(var i=1;i<trs.length;){ var trOne=trs[i];//表格的每一行 queryResultTable_obj.removeChild(trOne);//从表格中删除tr } }else{ var length= queryResultTable_obj.rows.length ; //表格最后一行索引 while(length > 1){ length--; if(length<1){ break; } queryResultTable_obj.deleteRow(length); } } }
使用spring MVC框架时,如何使用注解返回json呢? Java代码 @ResponseBody @RequestMapping(value = "/login") public ModelAndView ajaxLogin(Model model,User user,HttpServletRequest request, HttpSession session){ String errorMessage=loginCommon(model, user, request, session); Map map=new HashMap(); if(ValueWidget.isNullOrEmpty(errorMessage)){ map.put(Constant2.AJAX_LOGIN_RESULT, "success"); }else{ map.put(Constant2.AJAX_LOGIN_RESULT, "failed"); } map.put("error", errorMessage); model.addAttribute("user", null); return new ModelAndView(new MappingJacksonJsonView(),map); } 注意:使用如下方式也可以把内容添加到json中 Java代码 model.addAttribute("user", user1); 运行结果:
Java 中replaceAll如何忽略大小写呢? 方式一:在正则表达式前面添加(?i) Java代码 @Test public void test_replaceAll33(){ String input = "I like Java,jAva is very easy and jaVa is so popular."; String replacement="cccc"; System.out.println(input); System.out.println(input.replaceAll("(?i)java", replacement)); } 测试结果: 方式二:使用Matcher 的appendReplacement 方法 Java代码 @Test public void test_replaceAll(){ String input = "I like Java,jAva is very easy and jaVa is so popular."; String regex = "java"; String replacement="cccc"; StringBuffer sb =replaceAll2(input, regex, replacement); System.out.println(input); System.out.println(sb); } /*** * replaceAll,忽略大小写 * @param input * @param regex * @param replacement * @return */ public StringBuffer replaceAll2(String input,String regex,String replacement){ Pattern p = Pattern.compile(regex,Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); StringBuffer sb = new StringBuffer(); boolean result = m.find(); while (result) { m.appendReplacement(sb, replacement); result = m.find(); } m.appendTail(sb); return sb; } 测试结果: 方式三:使用Matcher 的replaceAll Java代码 /*** * replaceAll,忽略大小写 * * @param input * @param regex * @param replacement * @return */ public String replaceAll3(String input, String regex, String replacement) { Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(input); String result = m.replaceAll(replacement); return result; } @Test public void test_replaceAll3() { String input = "I like Java,jAva is very easy and jaVa is so popular."; String regex = "java"; String replacement = "cccc"; String sb = replaceAll3(input, regex, replacement); System.out.println(input); System.out.println(sb); } 测试结果: 参考:http://hw1287789687.iteye.com/blog/2150892
spring MVC中如何设置应答体的content type呢? spring MVC中如何设置返回类型呢? 我们知道response 的content type主要有: text/html text/plain application/json;charset=UTF-8 application/octet-stream 等 先举一个例子,spring mvc中可以通过如下方式返回json字符串: Java代码 @ResponseBody @RequestMapping(value = "/upload") public String upload(HttpServletRequest request, HttpServletResponse response,String contentType2) throws IOException { String content = null; Map map = new HashMap(); ObjectMapper mapper = new ObjectMapper(); map.put("fileName", "a.txt"); try { content = mapper.writeValueAsString(map); System.out.println(content); } catch (JsonGenerationException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return content; } 虽然访问时返回的确实是json字符串,但是response 的content type是" text/html "这不是我们期望的,我们期望的response content type是"application/json"或者"application/json;charset=UTF-8",那么如何实现呢? 通过注解@RequestMapping 中的produces 用法如下: Java代码 @RequestMapping(value = "/upload",produces="application/json;charset=UTF-8") spring MVC官方文档: Producible Media Types You can narrow the primary mapping by specifying a list of producible media types. The request will be matched only if the Accept request header matches one of these values. Furthermore, use of the produces condition ensures the actual content type used to generate the response respects the media types specified in the producescondition. For example: @Controller @RequestMapping(value = "/pets/{petId}", method = RequestMethod.GET, produces="application/json") @ResponseBody public Pet getPet(@PathVariable String petId, Model model) { // implementation omitted } Just like with consumes, producible media type expressions can be negated as in !text/plain to match to all requests other than those with an Accept header value oftext/plain. Tip The produces condition is supported on the type and on the method level. Unlike most other conditions, when used at the type level, method-level producible types override rather than extend type-level producible types.
Java web中如何跨域请求呢? 使用jsonp,详情请参考:http://json-p.org/ 页面代码如下: Html代码 <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <script type="application/javascript" > function jsonpCallback(result) { alert(JSON.stringify(result)); /*for(var i in result) { alert(i+":"+result[i]);//循环输出a:1,b:2,etc. } */ } var JSONP=document.createElement("script"); JSONP.type="text/javascript"; JSONP.src="http://192.168.0.100:8080/tv_mobile/video/text2?callback=jsonpCallback"; document.getElementsByTagName("head")[0].appendChild(JSONP); </script> </head> <body> </body> </html> 在浏览器中访问的效果: 后台采用spring mvc: Java代码 @ResponseBody @RequestMapping(value = "/text2",produces=SystemHWUtil.RESPONSE_CONTENTTYPE_JAVASCRIPT2 ) public String text2(HttpServletRequest request, HttpServletResponse response,String contentType2,String callback) throws IOException { String content = null; Map map = new HashMap(); map.put("fileName", "a.txt"); content=JSONPUtil.getJsonP(map, callback); System.out.println(content); return content; } JSONPUtil.getJsonP 静态方法的实现如下: Java代码 /*** * 用于jsonp调用 * @param map : 用于构造json数据 * @param callback : 回调的javascript方法名 * @return */ public static String getJsonP(Map map,String callback) { ObjectMapper mapper = new ObjectMapper(); String content = null; try { content = mapper.writeValueAsString(map); System.out.println(content); } catch (JsonGenerationException e) { e.printStackTrace(); } catch (JsonMappingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } if(ValueWidget.isNullOrEmpty(callback)){ return content; } return callback+"("+content+")"; } 依赖jackson 库 后台返回的内容是:jsonpCallback({"fileName":"a.txt"}) content type是 注意:后台返回的形式是:函数名(参数),此处的函数名就是回调函数的名称 参考: spring mvc设置应答体的content type AJAX 跨域请求 - JSONP获取JSON数据:http://justcoding.iteye.com/blog/1366102 App Framework发送JSONP请求(3): http://hw1287789687.iteye.com/blog/2190719
本人做Java Web开发4年了,对Java的"慢"深有感触,和PHP没法比啊. php修改代码之后,立刻生效,无需重启服务器; 而java,修改java文件之后,就得重新部署或者重新启动tomcat. 我一直主要使用eclipse进行java web开发,在编写代码过程中,不管你改了一个字符还是一个方法,甚至整个类,tomcat也会重启.改一点就得重启一次,真心觉得效率太低了,时间都浪费在tomcat重启了. 大家说的热部署或者重新发布实际上也相当于tomcat重启了,因为同样是把所有的class和资源文件加载一遍 其实关键点不是tomcat重不重启,而是每次都全量加载 现在用上了JRebel,tomcat重启的老毛病终于解决了! 为什么早几年没有人告诉我有JRebel 呢? eclipse 安装好JRebel插件之后,可以'帮助'中看到JRebel的图标: 进入JRebel Config CENTER: tomcat 的VM arguments 如下: ${jrebel_args} -Dcatalina.base="D:\software\eclipse\workspace2\.metadata\.plugins\org.eclipse.wst.server.core\tmp0" -Dcatalina.home="D:\software\apache-tomcat-7.0.59-windows-x64\apache-tomcat-7.0.59" -Dwtp.deploy="D:\software\eclipse\workspace2\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps" -Djava.endorsed.dirs="D:\software\apache-tomcat-7.0.59-windows-x64\apache-tomcat-7.0.59\endorsed" -Drebel.spring_plugin=true 好处: 当我们修改java文件时,妈妈再也不用担心tomcat重启了,而且JRebel也不会立刻重新装载修改的class, 只有当我们http访问时才会重新装载修改的类,而且仅仅装载修改的类.实际上是一种差量装载:
Java 中如何判断一个未知对象是否为空呢? 下面是一个通用的方法,判断字符串是否为空,集合是否为空,数组是否为空: Java代码 /** * 判断对象或对象数组中每一个对象是否为空: 对象为null,字符序列长度为0,集合类、Map为empty * * @param obj * @return */ public static boolean isNullOrEmpty(Object obj) { if (obj == null) return true; if (obj instanceof CharSequence) return ((CharSequence) obj).length() == 0; if (obj instanceof Collection) return ((Collection) obj).isEmpty(); if (obj instanceof Map) return ((Map) obj).isEmpty(); if (obj instanceof Object[]) { Object[] object = (Object[]) obj; if (object.length == 0) { return true; } boolean empty = true; for (int i = 0; i < object.length; i++) { if (!isNullOrEmpty(object[i])) { empty = false; break; } } return empty; } return false; } 参考:org.apache.commons.lang.StringUtils 应用场景: 读取excel文件,转化为一个二维数组:Object[][] arrays 但是excel中有空行,所以需要过滤Object[][] arrays中的空的一维数组: Java代码 /*** * 过滤数组中的空元素 * * * @param arrays * @return */ public static Object[][] filterEmpty(Object[][] arrays) { int sumNotNull = 0; /*** * 统计非空元素的总个数 */ for (int i = 0; i < arrays.length; i++) { Object object = arrays[i]; if (!ValueWidget.isNullOrEmpty(object) && !SystemUtil.isNullOrEmpty((Object[]) object)) {// 判断元素是否为空 sumNotNull = sumNotNull + 1; } } Object[][] filtedObjs = new Object[sumNotNull][]; int index = 0; for (int i = 0; i < arrays.length; i++) { Object[] object_tmp = arrays[i]; if (!ValueWidget.isNullOrEmpty(object_tmp) && !SystemUtil.isNullOrEmpty((Object[]) object_tmp)) {// 判断元素是否为空 filtedObjs[index++] = object_tmp; } } return filtedObjs; } 参阅附件中的方法com.common.util.SystemUtil.filterEmpty 判断对象的所有成员变量是否为空 Java代码 /*** * Determine whether the object's fields are empty * * @param obj * @param isExcludeZero :true:数值类型的值为0,则当做为空;----false:数值类型的值为0,则不为空 * * @return * @throws SecurityException * @throws IllegalArgumentException * @throws NoSuchFieldException * @throws IllegalAccessException */ public static boolean isNullObject(Object obj, boolean isExcludeZero) throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException { if(ValueWidget.isNullOrEmpty(obj)){//对象本身就为null return true; } List<Field> fieldList = ReflectHWUtils.getAllFieldList(obj.getClass()); boolean isNull = true; for (int i = 0; i < fieldList.size(); i++) { Field f = fieldList.get(i); Object propertyValue = null; try { propertyValue = getObjectValue(obj, f); } catch (NoSuchFieldException e) { e.printStackTrace(); } if (!ValueWidget.isNullOrEmpty(propertyValue)) {// 字段不为空 if (propertyValue instanceof Integer) {// 是数字 if (!((Integer) propertyValue == 0 && isExcludeZero)) { isNull = false; break; } } else if (propertyValue instanceof Double) {// 是数字 if (!((Double) propertyValue == 0 && isExcludeZero)) { isNull = false; break; } }else if (propertyValue instanceof Float) {// 是数字 if (!((Float) propertyValue == 0 && isExcludeZero)) { isNull = false; break; } }else if (propertyValue instanceof Short) {// 是数字 if (!((Short) propertyValue == 0 && isExcludeZero)) { isNull = false; break; } }else { isNull = false; break; } } } return isNull; } 测试: Java代码 @Test public void test_isNullObject() throws SecurityException, IllegalArgumentException, NoSuchFieldException, IllegalAccessException { Person2 p = new Person2(); Assert.assertEquals(true, ReflectHWUtils.isNullObject(p, true)); Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false)); p.setAddress("beijing"); Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, true)); Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false)); p.setAddress(null); p.setId(0); Assert.assertEquals(true, ReflectHWUtils.isNullObject(p, true)); Assert.assertEquals(false, ReflectHWUtils.isNullObject(p, false)); } Person2 源代码(省略getter,setter方法): Java代码 import java.sql.Timestamp; public class Person2 { private int id; private int age; private double weight; private String personName; private Timestamp birthdate; public String identitify; protected String address; String phone; }
Java 中getDeclaredFields() 与getFields() 的区别 getDeclaredFields()返回Class中所有的字段,包括私有字段。例证: Java代码 package com.test.bean; import java.sql.Timestamp; public class Person2 { private int id; private int age; private String personName; private Timestamp birthdate; public String identitify; protected String address; String phone; } @Test public void test_getDeclaredFields() { Field[]fields=Person2.class.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; System.out.println(field.getName()); } } 运行结果: id age personName birthdate identitify address phone getFields 只返回公共字段,即有public修饰的字段。例证: Java代码 @Test public void test_getDeclaredFields() { Field[]fields=Person2.class.getFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; System.out.println(field.getName()); } } 运行结果如下: identitify 总结: (1)getDeclaredFields()返回Class中所有的字段,包括私有字段; (2)getFields 只返回公共字段,即有public修饰的字段
使用eclipse 如何从svn服务器导入maven项目呢? 步骤如下: 1,点击菜单中的“File”,点击“Import”,如下图所示: 2,点击“Import”,弹出如下对话框,下面是错误的: 3,应该选择“Maven”下面的"Check out Maven Projects from SCM",如下图所示:
如何使用maven 打包源代码呢? 方式一: 打开cmd命令行,进入项目所在路径,运行 mvn source:jar 方式二:使用IDE,如eclipse 步骤: (1)右键点击项目,“Run as”,选择“Maven build...”,如下图 运行结果:
Jsp 中如何获取spring的bean呢? 方式一:通过上下文 Html代码 <?xml version="1.0" encoding="UTF-8" ?> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%> <%@page import="org.springframework.context.ApplicationContext"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Insert title here</title> </head> <body> <% ServletContext context = request.getSession().getServletContext(); ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(context); Object <span style="font-size: 1em; line-height: 1.5;">supermarketDao</span><span style="font-size: 1em; line-height: 1.5;">= ctx.getBean("supermarketDao");</span> System.out.println("jsp:"+<span style="font-size: 1em; line-height: 1.5;">supermarketDao</span><span style="font-size: 1em; line-height: 1.5;">);</span> %> </body> </html> 问题:JSP页面中获取的bean与spring 容器中的bean是同一个吗? 是的。 方式二:通过类路径加载bean文件,得到bean工厂 Html代码 <?xml version="1.0" encoding="UTF-8" ?> <%@page import="org.springframework.beans.factory.BeanFactory"%> <%@page import="org.springframework.context.support.ClassPathXmlApplicationContext"%> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@page import="org.springframework.web.context.support.WebApplicationContextUtils"%> <%@page import="org.springframework.context.ApplicationContext"%> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Insert title here</title> </head> <body> <% ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("beans.xml","user_beans.xml","goods_beans.xml","supermarket_beans.xml","aop.xml","upload_beans.xml"); BeanFactory factory = (BeanFactory) appContext; Object obj=factory.getBean("supermarketDao"); System.out.println("jsp2:"+obj); %> </body> </html> 问题:JSP页面中获取的bean与spring 容器中的bean是同一个吗? 不是的。 总结:(1)通过WebApplicationContextUtils 获取bean是直接从spring容器中拿的; (2)通过ClassPathXmlApplicationContext,实际上又解析了一遍xml,即又创建了一个新的spring容器,所有获取的bean与web上下文中是不同的。
使用Python 如何把数据写入文件呢?或者说如何把数据持久化呢? 方式一:使用file Python代码 #!/usr/bin/python poem='abc\n' f=file('poem.txt','w') f.write(poem) f.close() 注意:file()的第二个参数,“w”表示以“写”的方式打开文件 方式二:使用open Python代码 >>> a=['a\n','111\n','yyy\n'] >>> a ['a\n', '111\n', 'yyy\n'] >>> f=open('c.txt','w') >>> f.writelines(a) >>> f.close 注意:open()的第二个参数,“w”表示以“写”的方式打开文件 方式三:(仅适用于python3) Python代码 >>> man_file=open('man_data.txt','w') >>> print(['abc','\n'],file=man_file) >>> man_file.close() >>> root@ function_study# ls man_data.txt nest.py root@ function_study# cat man_data.txt ['abc', '\n']
Java 中,如何判断字符串是否是整数呢? 有两种方式: 方式一:通过正则表达式 Java代码 /*** * 判断 String 是否int * * @param input * @return */ public static boolean isInteger(String input){ Matcher mer = Pattern.compile("^[0-9]+$").matcher(input); return mer.find(); } 上述方法不完善,不能识别负数,比如识别不了“-9”,“+9”。多谢大家指教,改进如下: Java代码 /*** * 判断 String 是否是 int * * @param input * @return */ public static boolean isInteger(String input){ Matcher mer = Pattern.compile("^[+-]?[0-9]+$").matcher(input); return mer.find(); } 测试代码如下: Java代码 @Test public void test_isInteger(){ String input="123"; System.out.println(input+":"+ValueWidget.isInteger(input) ); input="000000000000009"; System.out.println(input+":"+ValueWidget.isInteger(input) ); input="-9"; System.out.println(input+":"+ValueWidget.isInteger(input) ); input="-09"; System.out.println(input+":"+ValueWidget.isInteger(input) ); input="--9"; System.out.println(input+":"+ValueWidget.isInteger(input) ); } 运行结果: 123:true 000000000000009:true -9:true -09:true --9:false 方式二:通过java 的异常 Java代码 public static boolean isValidInt(String value) { try { Integer.parseInt(value); } catch (NumberFormatException e) { return false; } return true; }
Java ,如何获取指定目录下的所有文件呢? 看代码: Java代码 /*** * 获取指定目录下的所有的文件(不包括文件夹),采用了递归 * * @param obj * @return */ public static ArrayList<File> getListFiles(Object obj) { File directory = null; if (obj instanceof File) { directory = (File) obj; } else { directory = new File(obj.toString()); } ArrayList<File> files = new ArrayList<File>(); if (directory.isFile()) { files.add(directory); return files; } else if (directory.isDirectory()) { File[] fileArr = directory.listFiles(); for (int i = 0; i < fileArr.length; i++) { File fileOne = fileArr[i]; files.addAll(getListFiles(fileOne)); } } return files; } 说明:上述方法采用了递归,所以包含子目录下的子目录中的文件。。。 测试代码: Java代码 @Test public void test_getListFiles(){ ArrayList<File> files=FileUtils.getListFiles("d:\\Temp\\a\\a"); SystemUtil.printFilesFilePath(files); } 输出结果: d:\Temp\a\a\divided\merged\oracle学习笔记.doc d:\Temp\a\a\divided\oracle学习笔记.doc_1_3kldv d:\Temp\a\a\divided\oracle学习笔记.doc_2_3kldv d:\Temp\a\a\divided\oracle学习笔记.doc_3_3kldv d:\Temp\a\a\oracle学习笔记.doc 过滤前缀(只是获取指定目录下的文件,没有递归): Java代码 /*** * * @param path * @param prefixStr * :前缀名 * @return */ public static File[] getFilesByPathPrefix(File path, final String prefixStr) { File[] fileArr = path.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { // System.out.println("prefixStr:"+prefixStr); if ((ValueWidget.isNullOrEmpty(prefixStr) || (dir.isDirectory() && name .startsWith(prefixStr)))) { return true; } else { return false; } } }); return fileArr; } /*** * 前缀名 * * @param pathStr * @param prefixStr * @return */ public static File[] getFilesByPathAndPrefix(String pathStr, final String prefixStr) { File path = new File(pathStr); return getFilesByPathPrefix(path, prefixStr); } 过滤后缀名(只是获取指定目录下的文件,没有递归): Java代码 /*** * * @param path * @param prefixStr * :后缀名 * @return */ public static File[] getFilesByPathAndSuffix(File path, final String sufixStr) { File[] fileArr = path.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { // System.out.println("prefixStr:"+prefixStr); if ((ValueWidget.isNullOrEmpty(sufixStr) || (dir.isDirectory() && name .endsWith(sufixStr)))) { return true; } else { return false; } } }); return fileArr; } /*** * 后缀名 * * @param pathStr * @param sufixStr * @return */ public static File[] getFilesByPathAndSuffix(String pathStr, final String sufixStr) { File path = new File(pathStr); return getFilesByPathAndSuffix(path, sufixStr); }
spring MVC要注意的地方: 控制器代码如下: Java代码 package com.mvc.jn.controller; import java.util.Map; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.servlet.ModelAndView; @org.springframework.stereotype.Controller public class HelloWorldController { // http://localhost:8088/springMVCannotations/hello2 @RequestMapping(value = "/hello2", method = RequestMethod.GET) public String sayHello2( @RequestParam(value = "name", required = false) String username, Map<String, Object> map) { map.put("message1", username); System.out.println("my name:" + username); return "hello22"; } // http://localhost:8088/springMVCannotations/hello3 @RequestMapping(value = "/hello3", method = RequestMethod.GET) public ModelAndView sayHello3( @RequestParam(value = "name", required = false) String username) { System.out.println("my name:" + username); ModelAndView mav = new ModelAndView("hello33"); mav.getModel().put("message1", username); return mav; } // http://localhost:8088/springMVCannotations/hello44?name=whuang @RequestMapping(value = "/hello44", method = RequestMethod.GET) // 与网上说的不一致,网上说方法名就是试图 public Model hello4( @RequestParam(value = "name", required = false) String username, Model model) { System.out.println("my name:" + username); model.addAttribute("message1", username); return model; } @RequestMapping(value = "/hello555", method = RequestMethod.GET) public Model hello5(String username, Model model) { System.out.println("my name:" + username); model.addAttribute("message1", username); return model; } } ViewResolver配置如下: Xml代码 <!-- ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> 问题1:调用控制器中的hello4 方法后会返回那个视图? 按照网上的说法,会返回“hello4”(方法名),如下图 但是经过我反复测试,发现它返回的视图是“hello44”,即@RequestMapping 指定的value。 问题2:访问方法hello5时,必须传参数username 吗? 答:不是必须的。 什么情况下是必须的呢? 当有注解@RequestParam(value = "username")时才是必须的,若不传该参数将报错. url 是“http://localhost:8088/springMVCannotations/hello555?username=ccc”时,会自动设置username的值 问题3:spring MVC与hibernate4集成时报错:No Session found for current thread 详情请参阅我的另一篇博客:http://hw1287789687.iteye.com/blog/1949852 问题4:启动tomcat时报错:Java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet 原因是:pom.xml中缺少: Xml代码 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>3.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.2.3.RELEASE</version> </dependency> 问题5:spring MVC 使用bean接收参数时如何传参? 比如查询时,controller 把参数注入到bean中, Java代码 @RequestMapping(value = "/show") public String show(Model model,User user,UserView view,HttpSession session) throws CloneNotSupportedException { if(!ValueWidget.isNullOrEmpty(view.getPageFlag())&&view.getPageFlag().equals(Constant2.PAGEFLAG_NOT_QUERY)){ System.out.println("不是查询"); user=(User)session.getAttribute("user2"); try { BeanUtils.copyProperties(view, user); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }else{//查询 System.out.println("是查询"); session.setAttribute("user2", user); } PageAssistant.paging(user,true,view, userDao); model.addAttribute("view", view); model.addAttribute("currentTime", TimeHWUtil.getCurrentTimestamp().getTime()); return "user/show"; } 方法show()的参数user会保存请求要素,那么在页面上是如何编写表单控件name的呢? Java代码 <Li> <label>用户名:</label><input type="text" name="user.username" value="${view.username }" /> </Li> <Li> <label>真实姓名:</label><input type="text" name="user.name" value="${view.name }" /> </Li> 上述代码是不对的,应该是: <Li> <label>用户名:</label><input type="text" name="username" value="${view.username }" /> </Li> <Li> <label>真实姓名:</label><input type="text" name="name" value="${view.name }" /> </Li>
项目是使用spring MVC (1)在浏览器中访问,后台总报错: Java代码 No mapping found for HTTP request with URI [/exam3/welcome] in DispatcherServlet with name 'spring2' 查了好半天,才发现是controller 没有扫描到。 我是使用的注解。 spring mvc配置文件如下: Xml代码 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd" > <!-- HandlerMapping --> <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping" /> <!-- HandlerAdapter --> <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter" /> <!-- ViewResolver --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> <property name="suffix" value=".jsp" /> </bean> <mvc:annotation-driven /> <!-- 处理器 --> <!-- <bean name="/hello" class="com.mvc.jn.controller.HelloWorldController"/> --> <context:component-scan base-package="com"/> </beans> controller 的目录结构如下: (2)这个问题解决之后,又报错: No mapping found for HTTP request with URI [/exam3/WEB-INF/jsp/welcome.jsp] in DispatcherServlet with name 'spring2' 结果发现是web.xml配置得有问题,下面是有问题的: Xml代码 <servlet> <servlet-name>spring2</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>spring2</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> 解决方法:把url-pattern 由/* 改为/
下面总结一下struts2 中if标签的使用 (1)判断字符串是否为空 <s:if test="user.username==null or user.username==''"> <s:if test='ab123==null'> ab123 is null </s:if> <s:else> ab123 not null </s:else> (2) 判断字符串是否为指定值 <s:if test="user.username eq \"a\""> is a </s:if> <s:else> not a </s:else> 或者: <s:if test="user.username==\"a\""> 或者: <s:if test='user.username eq "a"'> 使用单引号把双引号括起来 下面的是错误的: <s:if test="user.username=='a'"> <s:if test="user.username eq 'a'"> (3) 判断list是否有值 <s:if test="null==list2 or 0==list2.size">为空</s:if> <s:else>有值</s:else> (4) 判断数字是否为负数 <s:if test="user.age lt 0">小于0</s:if> <s:else>大于零</s:else> (5) 判断list的长度是否为指定值 <s:if test="2==list2.size">为2</s:if> <s:else>不为2</s:else>
项目使用springframework 3.2.3.RELEASE,hibernate 4.2.2.Final,使用spring MVC 项目名:exam3 使用数据库:MySQL 和数据库交互时报错: Xml代码 严重: Servlet.service() for servlet [spring2] in context with path [/exam3] threw exception [Request processing failed; nested exception is org.hibernate.HibernateException: No Session found for current thread] with root cause org.hibernate.HibernateException: No Session found for current thread at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:97) at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:988) at com.dao.UserDao.getByIdentity(UserDao.java:26) at com.dao.UserDao.getByIdentityAndStudentID(UserDao.java:42) at com.web.controller.LoginController.login(LoginController.java:56) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.springframework.web.method.support.InvocableHandlerMethod.invoke(InvocableHandlerMethod.java:219) at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:132) at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:745) at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:686) at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:80) at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:925) at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:856) at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:936) at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:827) at javax.servlet.http.HttpServlet.service(HttpServlet.java:621) at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:812) at javax.servlet.http.HttpServlet.service(HttpServlet.java:728) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:222) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:123) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:472) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:99) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:953) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1023) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:589) at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:312) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) at java.lang.Thread.run(Thread.java:662) 页面报错如下: 终于找到原因了,是两个配置文件(beans.xml,spring2-servlet.xml)中context:component-scan配置得有问题. 项目目录结构如下: spring2-servlet.xml 中的部分代码如下: Xml代码 <context:component-scan base-package="com"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> beans.xml中部分代码如下: Xml代码 <context:component-scan base-package="com" > <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> 值得注意的是:com目录下既有DAO,又有spring MVC的控制器。 解决方案 按如下方式修改之后就OK了: spring2-servlet.xml: Xml代码 <context:component-scan base-package="com.web"> <!-- <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> --> </context:component-scan> beans.xml: Xml代码 <context:component-scan base-package="com.dao" > <!-- <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> --> </context:component-scan> 注意: (1)spring集成hibernate的配置文件是beans.xml;spring MVC的配置文件是spring2-servlet.xml; (2)以上两个配置文件中均有context:component-scan 标签
spring MVC如何接收表单bean 呢? 之前项目中MVC框架一直用struts2,所以我也就按照struts2 的思维来思考 页面loginInput.jsp: Html代码 <?xml version="1.0" encoding="UTF-8" ?> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% String path = request.getContextPath(); String basePath = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + path + "/"; %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Insert title here</title> </head> <body> <center> <font color="red" >${message }</font> <form action="<%=path %>/user/loginVerify"> <table> <tr> <td>身份证:</td> <td> <input type="text" name="user.identity" /> </td> </tr> <tr> <td>用户编号:</td> <td><input type="text" name="userstudentID" /> </td> </tr> <tr> <td colspan="2"> <input type="submit" value="login"/> </td> </tr> </table> </form> </center> </body> </html> 控制器LoginController 中登录的方法: Java代码 /*** * 校验登录用户 * * @param session * @param user * @return * @throws UnsupportedEncodingException * @throws Exception */ @RequestMapping(value = "/loginVerify") public String login(User user, HttpSession session, Map<String, Object> map,Model model) throws UnsupportedEncodingException, Exception { User user2 = null; if (user.getIdentity() == null) { map.put("message", "请输入身份证"); return "loginInput"; } map.put("identity", user.getIdentity()); model.addAttribute("identity", user.getIdentity()); System.out.println("identity:"+session.getAttribute("identity")); user2 = this.userDao.getByIdentityAndStudentID(new User(user.getIdentity(), user.getStudentID())); System.out.println("user2:" + user2); if (user2 != null) { return "welcome"; } else { map.put("message", "身份证和用户编号有误,请重新登录"); return "loginInput"; } } 我认为页面表单中name为user.identity 和user.studentID的元素会自动注入到上述方法的变量User user 中,结果没有!!!? 实体类User: Java代码 package com.springmvc.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; /*** * father class * @author huangwei * */ @Entity public class User { private int id; /** * 身份证 */ private String identity; /*** * 用户编号 */ private String studentID; private String username; public User() { super(); } public User(String identity, String studentID) { super(); this.identity = identity; this.studentID = studentID; } @Id @GeneratedValue public int getId() { return id; } public void setId(int id) { this.id = id; } public String getIdentity() { return identity; } public void setIdentity(String identity) { this.identity = identity; } public String getStudentID() { return studentID; } public void setStudentID(String studentID) { this.studentID = studentID; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } } 原来,spring MVC 跟struts2的注入方式不一样!! 后来我把页面中的name属性改为identity 和studentID 就好了: <tr> <td>身份证:</td> <td> <input type="text" name="identity" /> </td> </tr> <tr> <td>用户编号:</td> <td><input type="text" name="studentID" /> </td> </tr> 这就是spring MVC与struts2 ioc不同的地方!
项目使用maven 构建,并且使用集成测试(integration-test)。 在命令行中运行mvn clean deploy tomcat:undeploy时,报错: Xml代码 ------------------------------------------------------- T E S T S ------------------------------------------------------- Running com.tdr.test.IntegrationTest This is integration-test $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ log4j:WARN Error during default initialization java.lang.NoClassDefFoundError: org/w3c/dom/ElementTraversal at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631) at java.lang.ClassLoader.defineClass(ClassLoader.java:615) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141) at java.net.URLClassLoader.defineClass(URLClassLoader.java:283) at java.net.URLClassLoader.access$000(URLClassLoader.java:58) at java.net.URLClassLoader$1.run(URLClassLoader.java:197) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) at org.apache.xerces.parsers.AbstractDOMParser.startDocument(Unknown Source) at org.apache.xerces.impl.dtd.XMLDTDValidator.startDocument(Unknown Source) at org.apache.xerces.impl.XMLDocumentScannerImpl.startEntity(Unknown Source) at org.apache.xerces.impl.XMLVersionDetector.startDocumentParsing(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source) at org.apache.xerces.parsers.XMLParser.parse(Unknown Source) at org.apache.xerces.parsers.DOMParser.parse(Unknown Source) at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source) at javax.xml.parsers.DocumentBuilder.parse(DocumentBuilder.java:180) at org.apache.log4j.xml.DOMConfigurator$2.parse(DOMConfigurator.java:690) at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:789) at org.apache.log4j.xml.DOMConfigurator.doConfigure(DOMConfigurator.java:696) at org.apache.log4j.helpers.OptionConverter.selectAndConfigure(OptionConverter.java:471) at org.apache.log4j.LogManager.<clinit>(LogManager.java:125) at org.apache.log4j.Logger.getLogger(Logger.java:105) at org.apache.commons.logging.impl.Log4JLogger.getLogger(Log4JLogger.java:289) at org.apache.commons.logging.impl.Log4JLogger.<init>(Log4JLogger.java:109) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27) at java.lang.reflect.Constructor.newInstance(Constructor.java:513) at org.apache.commons.logging.impl.LogFactoryImpl.createLogFromClass(LogFactoryImpl.java:1116) at org.apache.commons.logging.impl.LogFactoryImpl.discoverLogImplementation(LogFactoryImpl.java:914) at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:604) at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:336) at org.apache.commons.logging.impl.LogFactoryImpl.getInstance(LogFactoryImpl.java:310) at org.apache.commons.logging.LogFactory.getLog(LogFactory.java:685) at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.<init>(ThreadSafeClientConnManager.java:136) at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.<init>(ThreadSafeClientConnManager.java:115) at org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager.<init>(ThreadSafeClientConnManager.java:94) at org.openqa.selenium.remote.internal.HttpClientFactory.getClientConnectionManager(HttpClientFactory.java:61) at org.openqa.selenium.remote.internal.HttpClientFactory.<init>(HttpClientFactory.java:48) at org.openqa.selenium.remote.HttpCommandExecutor.<init>(HttpCommandExecutor.java:100) at org.openqa.selenium.remote.HttpCommandExecutor.<init>(HttpCommandExecutor.java:81) at org.openqa.selenium.remote.service.DriverCommandExecutor.<init>(DriverCommandExecutor.java:46) at org.openqa.selenium.ie.InternetExplorerDriver.run(InternetExplorerDriver.java:182) at org.openqa.selenium.ie.InternetExplorerDriver.<init>(InternetExplorerDriver.java:174) at org.openqa.selenium.ie.InternetExplorerDriver.<init>(InternetExplorerDriver.java:147) at com.tdr.test.IntegrationTest.test01(IntegrationTest.java:25) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) at java.lang.reflect.Method.invoke(Method.java:597) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:264) at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153) at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:124) at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:200) at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:153) at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103) Caused by: java.lang.ClassNotFoundException: org.w3c.dom.ElementTraversal at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) ... 75 more Started InternetExplorerDriver server (32-bit) 解决方法: 在项目的pom中增加: Xml代码 <dependency> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> <version>1.4.01</version> </dependency>
项目采用maven构建,想使用findbugs-maven-plugin 插件进行代码静态分析 官网:http://findbugs.sourceforge.net/ a program which uses static analysis to look for bugs in Java code 项目目录结构如下: 最下面的文件就是maven 的配置文件pom.xml,类似于ant的build.xml文件,pom.xml内容如下: Xml代码 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.kunlunsoft</groupId> <artifactId>isChinese</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <checkstyle.config.location>checkstyle.xml</checkstyle.config.location> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <reporting> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <version>2.5.2</version> </plugin> </plugins> </reporting> </project> 此时target目录是空的。打开cmd,进入项目所在目录,运行mvn findbugs:findbugs 运行结果如下: 从上图(mvn findbugs:findbugs的运行结果) 来看,maven 并没有运行findbugs,为什么会这样呢? 我们看看findbugs官网是如何说明的: FindBugs requires JRE (or JDK) 1.5.0 or later to run. However, it can analyze programs compiled for any version of Java, from 1.0 to 1.8. The current version of FindBugs is 2.0.2, released on 10:49:15 EST, 10 December, 2012. We are very interested in getting feedback on how to improve FindBugs. File bug reports onour sourceforge bug tracker findbugs虽然是一个静态分析工具,但是它分析的不是java源代码(后缀名为.java),而是class文件(编译后的文件)。在运行mvn findbugs:findbugs 时,不会自动编译项目,即没有class文件,所以findbugs没有运行。 有的技术博客说:“clean findbugs:findbugs install ,这种写法是错的,可以运行的,但是并不产生findbugs报告”,说法是对的,但是并没有说明原因。 原因:运行clean后,class文件都被删除了,所以不会运行findbugs,或者说findbugs没有可分析的class文件,自然就没有产生分析结果。 使用maven运行findbugs前一定先编译,一定要有class文件! 解决方法: (1)mvn clean compile findbugs:findbugs (2)mvn clean test findbugs:findbugs (test会调用compile生命周期) 如下图: 运行完之后,target目录会增加如下文件:findbugsXml.xml 测试结果是xml格式的,不方便查看、展示. 我们希望以html格式来展示findbugs的运行结果(报告),如下图:
我的MacBook http服务 (1)nginx nginx home目录: /usr/local/Cellar/nginx/1.8.1 配置文件路径: /usr/local/etc/nginx/nginx.conf 启动nginx: sudo nginx -c /usr/local/etc/nginx/nginx.conf (2)Apache 配置文件:/etc/apache2/httpd.conf www目录:/Library/WebServer/Documents/static (3)tomcat tomcat 安装目录: /Users/whuanghkl/software/apache-tomcat-8.0.32 端口号:8082 (4)Python web server python3: python -m http.server 8088 python2:python -m SimpleHTTPServer 8088 参考:http://angusjune.github.io/blog/2014/08/16/python-3-dot-x-no-module-named-simplehttpserver/
网页内容较少时footer没有贴底 于是设置其中的div最小高度,设置完之后,在大屏显示ok了. 但是在小屏显示不对了. $(window).height():窗口可视高度,不包括滚动条 $(document).height():整个网页的高度,包括滚动条 $('body').height():实际内容的高度 大屏中,内容没有填满 window :955 document:955 body: 779 所以可以拿body 和window 比较 如果body <window ,说明没有填满
JavaScript获取事件源 Js代码 var node = evt.currentTarget || evt.srcElement; var obj = event.srcElement ? event.srcElement : event.target; 加强版: Js代码 var eventTarget = event.srcElement || event.target || event.toElement|| event.currentTarget 范例: Js代码 var event = window.event || ev;//兼容ie7 8 cleanUpDefaultEvent(event);//清除默认事件 var target2 = event.srcElement || event.target || event.toElement; var position = {x: 0, y: 0}; var imgObj = target2; Js代码 getRelativePosition = helpers.getRelativePosition = function(evt){ var mouseX, mouseY; var e = evt.originalEvent || evt, canvas = evt.currentTarget || evt.srcElement||evt.target||evt.toElement, boundingRect = canvas.getBoundingClientRect(); if (e.touches){ mouseX = e.touches[0].clientX - boundingRect.left; mouseY = e.touches[0].clientY - boundingRect.top; } else{ mouseX = e.clientX - boundingRect.left; mouseY = e.clientY - boundingRect.top; } return { x : mouseX, y : mouseY }; } 封装成为函数: Js代码 /*** * 获取事件源 * @param evt * @returns {*|Object} */ getSrcElement = function (evt) { var event = window.event || evt;//兼容ie7 8 var eventTarget = event.srcElement || event.target || event.toElement || event.currentTarget; return eventTarget; };
gem install jekyll blog gem install jekyll Fetching: safe_yaml-1.0.4.gem (100%) Successfully installed safe_yaml-1.0.4 Fetching: rouge-1.10.1.gem (100%) Successfully installed rouge-1.10.1 Fetching: mercenary-0.3.6.gem (100%) Successfully installed mercenary-0.3.6 Fetching: liquid-3.0.6.gem (100%) Successfully installed liquid-3.0.6 Fetching: kramdown-1.11.1.gem (100%) Successfully installed kramdown-1.11.1 Fetching: jekyll-watch-1.4.0.gem (100%) Successfully installed jekyll-watch-1.4.0 Fetching: jekyll-sass-converter-1.4.0.gem (100%) Successfully installed jekyll-sass-converter-1.4.0 Fetching: colorator-0.1.gem (100%) Successfully installed colorator-0.1 Fetching: jekyll-3.1.3.gem (100%) Successfully installed jekyll-3.1.3 Parsing documentation for safe_yaml-1.0.4 Installing ri documentation for safe_yaml-1.0.4 Parsing documentation for rouge-1.10.1 Installing ri documentation for rouge-1.10.1 Parsing documentation for mercenary-0.3.6 Installing ri documentation for mercenary-0.3.6 Parsing documentation for liquid-3.0.6 Installing ri documentation for liquid-3.0.6 Parsing documentation for kramdown-1.11.1 Installing ri documentation for kramdown-1.11.1 Parsing documentation for jekyll-watch-1.4.0 Installing ri documentation for jekyll-watch-1.4.0 Parsing documentation for jekyll-sass-converter-1.4.0 Installing ri documentation for jekyll-sass-converter-1.4.0 Parsing documentation for colorator-0.1 Installing ri documentation for colorator-0.1 Parsing documentation for jekyll-3.1.3 Installing ri documentation for jekyll-3.1.3 Done installing documentation for safe_yaml, rouge, mercenary, liquid, kramdown, jekyll-watch, jekyll-sass-converter, colorator, jekyll after 8 seconds 9 gems installed blog 创建项目: jekyll new whuanghkl-site 启动项目: jekyll serve blog jekyll new whuanghkl-site New jekyll site installed in /Users/whuanghkl/work/mygit/blog/whuanghkl-site. blog cd whuanghkl-site whuanghkl-site ls _config.yml _includes _layouts _posts _sass about.md css feed.xml index.html whuanghkl-site jekyll serve Configuration file: /Users/whuanghkl/work/mygit/blog/whuanghkl-site/_config.yml Source: /Users/whuanghkl/work/mygit/blog/whuanghkl-site Destination: /Users/whuanghkl/work/mygit/blog/whuanghkl-site/_site Incremental build: disabled. Enable with --incremental Generating... done in 0.281 seconds. Auto-regeneration: enabled for '/Users/whuanghkl/work/mygit/blog/whuanghkl-site' Configuration file: /Users/whuanghkl/work/mygit/blog/whuanghkl-site/_config.yml Server address: http://127.0.0.1:4000/ Server running... press ctrl-c to stop. 参考:http://jekyllrb.com/
在linux 中,如何遍历指定目录下的所有文件夹呢? 要求能搜索结果中包含隐藏文件夹 脚本名:ergodic_folder.sh 脚本内容: Shell代码 #!/bin/sh list_alldir(){ for file2 in `ls -a $1` do if [ x"$file2" != x"." -a x"$file2" != x".." ];then if [ -d "$1/$file2" ];then echo "$1/$file2" list_alldir "$1/$file2" fi fi done } list_alldir ./test 测试如下: [root@localhost whuang]# ./ergodic_folder.sh ./test/.abc ./test/.abc/.ccc ./test/bbb
我做的一个考试系统,使用了hibernate和spring,原来使用的tomcat版本是6,后来把tomcat换成了apache-tomcat-7.0.30-windows-x64,spring的版本是:3.1 结果启动tomcat时报错如下: .6. Feb 27, 2013 12:18:00 AM org.apache.catalina.core.AprLifecycleListener init INFO: APR capabilities: IPv6 [true], sendfile [true], accept filters [false], ra ndom [true]. Feb 27, 2013 12:18:01 AM org.apache.catalina.core.AprLifecycleListener initializ eSSL INFO: OpenSSL successfully initialized (OpenSSL 1.0.1c 10 May 2012) Feb 27, 2013 12:18:01 AM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["http-apr-8084"] Feb 27, 2013 12:18:01 AM org.apache.coyote.AbstractProtocol init INFO: Initializing ProtocolHandler ["ajp-apr-8009"] Feb 27, 2013 12:18:01 AM org.apache.catalina.startup.Catalina load INFO: Initialization processed in 1026 ms Feb 27, 2013 12:18:01 AM org.apache.catalina.core.StandardService startInternal INFO: Starting service Catalina Feb 27, 2013 12:18:01 AM org.apache.catalina.core.StandardEngine startInternal INFO: Starting Servlet Engine: Apache Tomcat/7.0.30 Feb 27, 2013 12:18:01 AM org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory C:\kunlunsoft\exam2\tomcat\webapps\doc s Feb 27, 2013 12:18:01 AM org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory C:\kunlunsoft\exam2\tomcat\webapps\exa m004 Feb 27, 2013 12:18:01 AM org.apache.catalina.loader.WebappClassLoader validateJa rFile INFO: validateJarFile(C:\kunlunsoft\exam2\tomcat\webapps\exam004\WEB-INF\lib\ser vlet-api.jar) - jar not loaded. See Servlet Spec 2.3, section 9.7.2. Offending c lass: javax/servlet/Servlet.class Feb 27, 2013 12:18:08 AM org.apache.catalina.core.ContainerBase addChildInternal SEVERE: ContainerBase.addChild: start: org.apache.catalina.LifecycleException: Failed to start component [StandardEngin e[Catalina].StandardHost[localhost].StandardContext[/exam004]] at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.Java:154) at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase .java:901) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:87 7) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:618) at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.jav a:1100) at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig .java:1618) at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) Caused by: java.lang.NullPointerException at org.springframework.web.SpringServletContainerInitializer.onStartup(S pringServletContainerInitializer.java:142) at org.apache.catalina.core.StandardContext.startInternal(StandardContex t.java:5274) at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150) ... 11 more Feb 27, 2013 12:18:08 AM org.apache.catalina.startup.HostConfig deployDirectory SEVERE: Error deploying web application directory C:\kunlunsoft\exam2\tomcat\web apps\exam004 java.lang.IllegalStateException: ContainerBase.addChild: start: org.apache.catal ina.LifecycleException: Failed to start component [StandardEngine[Catalina].Stan dardHost[localhost].StandardContext[/exam004]] at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase .java:904) at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:87 7) at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:618) at org.apache.catalina.startup.HostConfig.deployDirectory(HostConfig.jav a:1100) at org.apache.catalina.startup.HostConfig$DeployDirectory.run(HostConfig .java:1618) at java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source) at java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source) at java.util.concurrent.FutureTask.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source ) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source) Feb 27, 2013 12:18:08 AM org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory C:\kunlunsoft\exam2\tomcat\webapps\hos t-manager Feb 27, 2013 12:18:08 AM org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory C:\kunlunsoft\exam2\tomcat\webapps\man ager Feb 27, 2013 12:18:08 AM org.apache.catalina.startup.HostConfig deployDirectory INFO: Deploying web application directory C:\kunlunsoft\exam2\tomcat\webapps\ROO T Feb 27, 2013 12:18:08 AM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["http-apr-8084"] Feb 27, 2013 12:18:08 AM org.apache.coyote.AbstractProtocol start INFO: Starting ProtocolHandler ["ajp-apr-8009"] Feb 27, 2013 12:18:08 AM org.apache.catalina.startup.Catalina start INFO: Server startup in 7167 ms 解决办法: 后来 在网上查了好长时间,看到了如下文章: http://hi.baidu.com/renyijiu/item/9dfda05328afe7464eff20b7 把 spring 的版本更新为3.2.1.RELEASE ,问题就解决了。
struts2中 ValueStack的set方法与setValue方法的区别呢? 示例代码: Java代码 ActionContext.getContext().getValueStack().setValue("myname22", "ttt"); 区别: (1)setValue 方法必须要求有该属性的setter方法,否则会报错: Error setting expression'myname22' with value 'ttt' - [unknown location] set方法设置的属性与该action没有任何关系,所以就算action中没有该属性的setter方法,调用 Java代码 ActionContext.getContext().getValueStack().set("myname22", "ttt"); 也不会报错。 (2)setValue方法设置的是action的属性(action中有属性myname22),在value stack 中对应的是action的属性; 而set方法设置的属性会放在一个hashmap中,与当前的action没有任何瓜葛,但是两者都在value stack中,set方法设置的属性可以通过 <s:property value="myname22" />来取值。 共同点: (1)setValue和set方法设置的属性可以通过 Java代码 String myname2=(String)ServletActionContext.getContext().getValueStack().findValue("myname22"); 来取值; (2)在result指向的JSP页面中都可以通过 <s:property value="myname22" />来取值(setValue方法设置的属性必须要有对应的getter方法)。 action代码: Java代码 package example; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class GetValueAction extends ActionSupport { private static final long serialVersionUID = 4865100826143278474L; private String myname=null; // private String myname22; @Override public String execute() throws Exception { ActionContext.getContext().getValueStack().set("myname22", "ttt"); String myname2=(String)ServletActionContext.getContext().getValueStack().findValue("myname22"); // System.out.println("myname2: "+this.myname); return super.execute(); } public String getMyname() { return myname; } public void setMyname(String myname) { this.myname = myname; } // public String getMyname22() { // return myname22; // } // // public void setMyname22(String myname22) { // System.out.println("abc:"+myname22); // this.myname22 = myname22; // } // public String getMyname22() { // return myname22; // } } 总结:set方法和setValue方法设置的属性都可以通过<s:property value="myname22" />取值。
在Java 中,如何把二进制文件(如图片,ssl证书 )转化为字节数组呢? 代码如下: Java代码 @org.junit.Test public void test055() throws IOException { File inFile = new File("d:\\Chrysanthemum.jpg"); FileInputStream fileInputStream = new FileInputStream(inFile); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); int i; //转化为字节数组流 while ((i = fileInputStream.read()) != -1) { byteArrayOutputStream.write(i); } fileInputStream.close(); // 把文件存在一个字节数组中 byte[] filea = byteArrayOutputStream.toByteArray(); byteArrayOutputStream.close(); String encoding = "ISO-8859-1"; String fileaString = new String(filea, encoding); System.out.println(fileaString); // 写入文件 FileOutputStream fileOutputStream = new FileOutputStream("d:/b.png"); fileOutputStream.write(fileaString.getBytes(encoding)); fileOutputStream.flush(); fileOutputStream.close(); } 注意: (1)使用ByteArrayOutputStream 来把二进制流转化为字节数组流; (2)把字节数组转化为String类型时,一定要使用ISO-8859-1编码; String encoding = "ISO-8859-1"; String fileaString = new String(filea, encoding); (3)通过字符串获取字节数组时,一定要使用ISO-8859-1编码: fileOutputStream.write(fileaString.getBytes(encoding));
Java 中使用base64编码和解码: 第一种方式: 通过反射使用java 中不对外公开的类: Java代码 /*** * encode by Base64 */ public static String encodeBase64(byte[]input) throws Exception{ Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64"); Method mainMethod= clazz.getMethod("encode", byte[].class); mainMethod.setAccessible(true); Object retObj=mainMethod.invoke(null, new Object[]{input}); return (String)retObj; } /*** * decode by Base64 */ public static byte[] decodeBase64(String input) throws Exception{ Class clazz=Class.forName("com.sun.org.apache.xerces.internal.impl.dv.util.Base64"); Method mainMethod= clazz.getMethod("decode", String.class); mainMethod.setAccessible(true); Object retObj=mainMethod.invoke(null, input); return (byte[])retObj; } 第二种方式: 使用commons-codec.jar Java代码 /** * @param bytes * @return */ public static byte[] decode(final byte[] bytes) { return Base64.decodeBase64(bytes); } /** * 二进制数据编码为BASE64字符串 * * @param bytes * @return * @throws Exception */ public static String encode(final byte[] bytes) { return new String(Base64.encodeBase64(bytes)); } 第三种方式: Java代码 /** * 编码 * @param bstr * @return String */ public static String encode(byte[] bstr){ return new sun.misc.BASE64Encoder().encode(bstr); } /** * 解码 * @param str * @return string */ public static byte[] decode(String str){ byte[] bt = null; try { sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder(); bt = decoder.decodeBuffer( str ); } catch (IOException e) { e.printStackTrace(); } return bt; }
项目中使用了maven,而且搭建了nexus 私服。 经常会遇到如下问题: (一) 使用maven构建的项目下面都有一个pom.xml文件,里面设置了该项目的依赖的jar包。第一次因为没有联网或者nexus服务未启动,导致下载jar失败,以后依然下载失败。后来终于找到了原因: 因为第一次下载失败时,会在C:\Users\huangwei\.m2\repository 中生成对应jar包的lastUpdated文件,如xwork-core-2.1.6.jar.lastUpdated。因为有这个lastUpdated文件,所以以后都不会真正下载xwork-core-2.1.6.jar了。 解决方法:(1)在执行mvn compile 之前把lastUpdated 文件删除; (2)mvn compile -U (二)使用maven 编译(mvn compile)时,报错:maven source 1.3 中不支持泛型 解决方法:在项目的pom.xml中添加: Xml代码 <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.6</source> <target>1.6</target> </configuration> </plugin> </plugins> </build> (三)执行 mvn deploy时报错:Java.lang.OutOfMemoryError: Java heap space 解决方法: 执行deploy时指定jre的参数 -Xms128M -Xmx512M (四)使用nexus 时,eclipse中 Maven Repositories 视图中的Global Repositories下的nexus 无法展开 解决方法: 对nexus 中Public Repositories 先执行“Rebuild Metadata”,再执行“Update Index”: (五)使用eclipse 执行deploy部署项目时报错,报错信息如下: Xml代码 Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-deploy) on project demoManagement: Failed to deploy artifacts: Could not find artifact com.kunlunsoft.jn:demoManagement:war:1.1.0-RELEASE in tdr_vks_management_release (http://192.168.2.150:8088/nexus/content/repositories/kunlun_management_release/) -> [Help 1] 项目名为demoManagement 刚开始以为是pom。xml中finalName 引起的。结果发现nexus 压根儿没有叫“kunlun_management_release”的仓库。 根本原因:pom.xml中snapshotRepository指定的仓库不存在。
Java 中,如何从InputStream 读取字节数组呢? 方式一: Java代码 /*** * Has been tested * * @param in * @return * @throws IOException */ public static byte[] readBytes(InputStream in) throws IOException { byte[] temp = new byte[in.available()]; byte[] result = new byte[0]; int size = 0; while ((size = in.read(temp)) != -1) { byte[] readBytes = new byte[size]; System.arraycopy(temp, 0, readBytes, 0, size); result = SystemUtil.mergeArray(result,readBytes); } return result; } 方式二: Java代码 public static byte[] readBytes3(InputStream in) throws IOException { BufferedInputStream bufin = new BufferedInputStream(in); int buffSize = BUFFSIZE_1024; ByteArrayOutputStream out = new ByteArrayOutputStream(buffSize); // System.out.println("Available bytes:" + in.available()); byte[] temp = new byte[buffSize]; int size = 0; while ((size = bufin.read(temp)) != -1) { out.write(temp, 0, size); } bufin.close(); byte[] content = out.toByteArray(); return content; } 依赖的函数: Java代码 /*** * 合并字节数组 * @param a * @return */ public static byte[] mergeArray(byte[]... a) { // 合并完之后数组的总长度 int index = 0; int sum = 0; for (int i = 0; i < a.length; i++) { sum = sum + a[i].length; } byte[] result = new byte[sum]; for (int i = 0; i < a.length; i++) { int lengthOne = a[i].length; if(lengthOne==0){ continue; } // 拷贝数组 System.arraycopy(a[i], 0, result, index, lengthOne); index = index + lengthOne; } return result; }